similarIssues.spec.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import {GroupFixture} from 'sentry-fixture/group';
  2. import {GroupsFixture} from 'sentry-fixture/groups';
  3. import {ProjectFixture} from 'sentry-fixture/project';
  4. import {RouterFixture} from 'sentry-fixture/routerFixture';
  5. import {
  6. render,
  7. renderGlobalModal,
  8. screen,
  9. userEvent,
  10. waitFor,
  11. } from 'sentry-test/reactTestingLibrary';
  12. import ProjectsStore from 'sentry/stores/projectsStore';
  13. import GroupSimilarIssues from 'sentry/views/issueDetails/groupSimilarIssues/similarIssues';
  14. const MockNavigate = jest.fn();
  15. jest.mock('sentry/utils/useNavigate', () => ({
  16. useNavigate: () => MockNavigate,
  17. }));
  18. jest.mock('sentry/utils/analytics');
  19. describe('Issues Similar View', function () {
  20. let mock: jest.Mock;
  21. const project = ProjectFixture({
  22. features: ['similarity-view'],
  23. });
  24. const group = GroupFixture();
  25. const router = RouterFixture({
  26. params: {orgId: 'org-slug', groupId: group.id},
  27. });
  28. const scores = [
  29. {'exception:stacktrace:pairs': 0.375},
  30. {'exception:stacktrace:pairs': 0.01264},
  31. {'exception:stacktrace:pairs': 0.875},
  32. {'exception:stacktrace:pairs': 0.001488},
  33. ];
  34. const mockData = {
  35. similar: GroupsFixture().map((issue, i) => [issue, scores[i]]),
  36. };
  37. beforeEach(function () {
  38. mock = MockApiClient.addMockResponse({
  39. url: `/organizations/org-slug/issues/${group.id}/similar/?limit=50`,
  40. body: mockData.similar,
  41. });
  42. MockApiClient.addMockResponse({
  43. url: `/organizations/org-slug/issues/${group.id}/`,
  44. body: group,
  45. });
  46. ProjectsStore.init();
  47. ProjectsStore.loadInitialData([project]);
  48. });
  49. afterEach(() => {
  50. MockApiClient.clearMockResponses();
  51. jest.clearAllMocks();
  52. });
  53. const selectNthSimilarItem = async (index: number) => {
  54. const items = await screen.findAllByTestId('similar-item-row');
  55. const item = items.at(index);
  56. expect(item).toBeDefined();
  57. await userEvent.click(item!);
  58. };
  59. it('renders with mocked data', async function () {
  60. render(<GroupSimilarIssues />, {router});
  61. expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
  62. await waitFor(() => expect(mock).toHaveBeenCalled());
  63. expect(await screen.findByText('Show 3 issues below threshold')).toBeInTheDocument();
  64. });
  65. it('can merge and redirect to new parent', async function () {
  66. const merge = MockApiClient.addMockResponse({
  67. method: 'PUT',
  68. url: '/projects/org-slug/project-slug/issues/',
  69. body: {
  70. merge: {children: ['123'], parent: '321'},
  71. },
  72. });
  73. render(<GroupSimilarIssues />, {router});
  74. renderGlobalModal();
  75. await selectNthSimilarItem(0);
  76. await userEvent.click(await screen.findByRole('button', {name: 'Merge (1)'}));
  77. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  78. await waitFor(() => {
  79. expect(merge).toHaveBeenCalledWith(
  80. '/projects/org-slug/project-slug/issues/',
  81. expect.objectContaining({
  82. data: {merge: 1},
  83. })
  84. );
  85. });
  86. expect(MockNavigate).toHaveBeenCalledWith(
  87. '/organizations/org-slug/issues/321/similar/'
  88. );
  89. });
  90. it('correctly shows merge count', async function () {
  91. render(<GroupSimilarIssues />, {router});
  92. renderGlobalModal();
  93. await selectNthSimilarItem(0);
  94. expect(screen.getByText('Merge (1)')).toBeInTheDocument();
  95. // Correctly show "Merge (0)" when the item is un-clicked
  96. await selectNthSimilarItem(0);
  97. expect(screen.getByText('Merge (0)')).toBeInTheDocument();
  98. });
  99. it('shows empty message', async function () {
  100. mock = MockApiClient.addMockResponse({
  101. url: `/organizations/org-slug/issues/${group.id}/similar/?limit=50`,
  102. body: [],
  103. });
  104. render(<GroupSimilarIssues />, {router});
  105. renderGlobalModal();
  106. await waitFor(() => expect(mock).toHaveBeenCalled());
  107. expect(
  108. await screen.findByText("There don't seem to be any similar issues.")
  109. ).toBeInTheDocument();
  110. expect(
  111. screen.queryByText(
  112. 'This can occur when the issue has no stacktrace or in-app frames.'
  113. )
  114. ).not.toBeInTheDocument();
  115. });
  116. });
  117. describe('Issues Similar Embeddings View', function () {
  118. let mock: jest.Mock;
  119. const group = GroupFixture();
  120. const project = ProjectFixture({
  121. features: ['similarity-view', 'similarity-embeddings'],
  122. });
  123. const router = RouterFixture({
  124. params: {orgId: 'org-slug', groupId: group.id},
  125. });
  126. const similarEmbeddingsScores = [
  127. {exception: 0.01, shouldBeGrouped: 'Yes'},
  128. {exception: 0.005, shouldBeGrouped: 'Yes'},
  129. {exception: 0.7384, shouldBeGrouped: 'No'},
  130. {exception: 0.3849, shouldBeGrouped: 'No'},
  131. ];
  132. const mockData = {
  133. simlarEmbeddings: GroupsFixture().map((issue, i) => [
  134. issue,
  135. similarEmbeddingsScores[i],
  136. ]),
  137. };
  138. beforeEach(function () {
  139. mock = MockApiClient.addMockResponse({
  140. url: `/organizations/org-slug/issues/${group.id}/similar-issues-embeddings/?k=10&threshold=0.01&useReranking=true`,
  141. body: mockData.simlarEmbeddings,
  142. });
  143. MockApiClient.addMockResponse({
  144. url: `/organizations/org-slug/issues/${group.id}/`,
  145. body: group,
  146. });
  147. ProjectsStore.init();
  148. ProjectsStore.loadInitialData([project]);
  149. });
  150. afterEach(() => {
  151. MockApiClient.clearMockResponses();
  152. jest.clearAllMocks();
  153. });
  154. const selectNthSimilarItem = async (index: number) => {
  155. const items = await screen.findAllByTestId('similar-item-row');
  156. const item = items.at(index);
  157. expect(item).toBeDefined();
  158. await userEvent.click(item!);
  159. };
  160. it('renders with mocked data', async function () {
  161. render(<GroupSimilarIssues />, {router});
  162. await waitFor(() => expect(mock).toHaveBeenCalled());
  163. expect(screen.queryByText('Show 3 issues below threshold')).not.toBeInTheDocument();
  164. });
  165. it('can merge and redirect to new parent', async function () {
  166. const merge = MockApiClient.addMockResponse({
  167. method: 'PUT',
  168. url: '/projects/org-slug/project-slug/issues/',
  169. body: {
  170. merge: {children: ['123'], parent: '321'},
  171. },
  172. });
  173. render(<GroupSimilarIssues />, {router});
  174. renderGlobalModal();
  175. await selectNthSimilarItem(0);
  176. await userEvent.click(await screen.findByRole('button', {name: 'Merge (1)'}));
  177. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  178. await waitFor(() => {
  179. expect(merge).toHaveBeenCalledWith(
  180. '/projects/org-slug/project-slug/issues/',
  181. expect.objectContaining({
  182. data: {merge: 1},
  183. })
  184. );
  185. });
  186. expect(MockNavigate).toHaveBeenCalledWith(
  187. '/organizations/org-slug/issues/321/similar/'
  188. );
  189. });
  190. it('correctly shows merge count', async function () {
  191. render(<GroupSimilarIssues />, {router});
  192. renderGlobalModal();
  193. await selectNthSimilarItem(0);
  194. expect(screen.getByText('Merge (1)')).toBeInTheDocument();
  195. // Correctly show "Merge (0)" when the item is un-clicked
  196. await selectNthSimilarItem(0);
  197. expect(screen.getByText('Merge (0)')).toBeInTheDocument();
  198. });
  199. it('shows empty message', async function () {
  200. mock = MockApiClient.addMockResponse({
  201. url: `/organizations/org-slug/issues/${group.id}/similar-issues-embeddings/?k=10&threshold=0.01&useReranking=true`,
  202. body: [],
  203. });
  204. render(<GroupSimilarIssues />, {router});
  205. renderGlobalModal();
  206. await waitFor(() => expect(mock).toHaveBeenCalled());
  207. expect(
  208. await screen.findByText(
  209. "There don't seem to be any similar issues. This can occur when the issue has no stacktrace or in-app frames."
  210. )
  211. ).toBeInTheDocument();
  212. });
  213. });