groupActivity.spec.jsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {
  3. act,
  4. render,
  5. renderGlobalModal,
  6. screen,
  7. userEvent,
  8. waitFor,
  9. } from 'sentry-test/reactTestingLibrary';
  10. import PullRequestLink from 'sentry/components/pullRequestLink';
  11. import ConfigStore from 'sentry/stores/configStore';
  12. import GroupStore from 'sentry/stores/groupStore';
  13. import OrganizationStore from 'sentry/stores/organizationStore';
  14. import ProjectsStore from 'sentry/stores/projectsStore';
  15. import TeamStore from 'sentry/stores/teamStore';
  16. import {GroupActivityType} from 'sentry/types';
  17. import {GroupActivity} from 'sentry/views/issueDetails/groupActivity';
  18. describe('GroupActivity', function () {
  19. let project;
  20. const dateCreated = '2021-10-01T15:31:38.950115Z';
  21. beforeEach(function () {
  22. project = TestStubs.Project();
  23. ProjectsStore.loadInitialData([project]);
  24. ConfigStore.init();
  25. ConfigStore.set('user', {id: '123'});
  26. GroupStore.init();
  27. });
  28. afterEach(() => {
  29. MockApiClient.clearMockResponses();
  30. jest.clearAllMocks();
  31. });
  32. function createWrapper({activity, organization: additionalOrg} = {}) {
  33. const group = TestStubs.Group({
  34. id: '1337',
  35. activity: activity ?? [
  36. {type: 'note', id: 'note-1', data: {text: 'Test Note'}, user: TestStubs.User()},
  37. ],
  38. project,
  39. });
  40. const {organization, routerContext} = initializeOrg({
  41. organization: additionalOrg,
  42. group,
  43. });
  44. GroupStore.add([group]);
  45. TeamStore.loadInitialData([TestStubs.Team({id: '999', slug: 'no-team'})]);
  46. OrganizationStore.onUpdate(organization, {replace: true});
  47. return render(
  48. <GroupActivity
  49. api={new MockApiClient()}
  50. params={{orgId: 'org-slug'}}
  51. group={group}
  52. organization={organization}
  53. />,
  54. {context: routerContext}
  55. );
  56. }
  57. it('renders a NoteInput', function () {
  58. createWrapper();
  59. expect(screen.getByTestId('activity-note-body')).toBeInTheDocument();
  60. });
  61. it('renders a marked reviewed activity', function () {
  62. const user = TestStubs.User({name: 'Samwise'});
  63. createWrapper({
  64. activity: [{type: 'mark_reviewed', id: 'reviewed-1', data: {}, user}],
  65. });
  66. expect(screen.getByText('marked this issue as reviewed')).toBeInTheDocument();
  67. expect(screen.getByText(user.name)).toBeInTheDocument();
  68. });
  69. it('renders a pr activity', function () {
  70. const user = TestStubs.User({name: 'Test User'});
  71. const repository = TestStubs.Repository();
  72. const pullRequest = TestStubs.PullRequest({message: 'Fixes ISSUE-1'});
  73. createWrapper({
  74. activity: [
  75. {
  76. type: 'set_resolved_in_pull_request',
  77. id: 'pr-1',
  78. data: {
  79. pullRequest: {
  80. author: 'Test User',
  81. version: (
  82. <PullRequestLink
  83. inline
  84. pullRequest={pullRequest}
  85. repository={pullRequest.repository}
  86. />
  87. ),
  88. repository: {repository},
  89. },
  90. },
  91. user,
  92. },
  93. ],
  94. });
  95. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  96. 'Test User has created a PR for this issue:'
  97. );
  98. });
  99. it('renders a assigned to self activity', function () {
  100. const user = TestStubs.User({id: '301', name: 'Mark'});
  101. createWrapper({
  102. activity: [
  103. {
  104. data: {
  105. assignee: user.id,
  106. assigneeEmail: user.email,
  107. assigneeType: 'user',
  108. },
  109. dateCreated: '2021-10-01T15:31:38.950115Z',
  110. id: '117',
  111. type: 'assigned',
  112. user,
  113. },
  114. ],
  115. });
  116. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  117. /Mark assigned this issue to themselves/
  118. );
  119. });
  120. it('renders an assigned via codeowners activity', function () {
  121. createWrapper({
  122. activity: [
  123. {
  124. data: {
  125. assignee: '123',
  126. assigneeEmail: 'anotheruser@sentry.io',
  127. assigneeType: 'user',
  128. integration: 'codeowners',
  129. rule: 'path:something/*.py #workflow',
  130. },
  131. dateCreated: '2021-10-01T15:31:38.950115Z',
  132. id: '117',
  133. type: 'assigned',
  134. user: null,
  135. },
  136. ],
  137. });
  138. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  139. /Sentry auto-assigned this issue to anotheruser@sentry.io/
  140. );
  141. });
  142. it('renders an assigned via slack activity', function () {
  143. const user = TestStubs.User({id: '301', name: 'Mark'});
  144. createWrapper({
  145. activity: [
  146. {
  147. data: {
  148. assignee: '123',
  149. assigneeEmail: 'anotheruser@sentry.io',
  150. assigneeType: 'user',
  151. integration: 'slack',
  152. },
  153. dateCreated: '2021-10-01T15:31:38.950115Z',
  154. id: '117',
  155. type: 'assigned',
  156. user,
  157. },
  158. ],
  159. });
  160. const item = screen.getAllByTestId('activity-item').at(-1);
  161. expect(item).toHaveTextContent(/Mark assigned this issue to anotheruser@sentry.io/);
  162. expect(item).toHaveTextContent(/Assigned via Slack/);
  163. });
  164. it('resolved in commit with no releases', function () {
  165. createWrapper({
  166. activity: [
  167. {
  168. type: 'set_resolved_in_commit',
  169. id: '123',
  170. data: {
  171. author: 'hello',
  172. commit: {
  173. id: 'komal-commit',
  174. repository: {},
  175. releases: [],
  176. },
  177. },
  178. user: TestStubs.User(),
  179. },
  180. ],
  181. });
  182. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  183. 'Foo Bar marked this issue as resolved in komal-commit'
  184. );
  185. });
  186. it('resolved in commit with one release', function () {
  187. createWrapper({
  188. activity: [
  189. {
  190. type: 'set_resolved_in_commit',
  191. id: '123',
  192. data: {
  193. author: 'hello',
  194. commit: {
  195. id: 'komal-commit',
  196. repository: {},
  197. releases: [
  198. {
  199. dateCreated: '2022-05-01',
  200. dateReleased: '2022-05-02',
  201. version: 'random',
  202. },
  203. ],
  204. },
  205. },
  206. user: TestStubs.User(),
  207. },
  208. ],
  209. });
  210. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  211. 'Foo Bar marked this issue as resolved in komal-commit This commit was released in random'
  212. );
  213. });
  214. it('resolved in commit with multiple releases', function () {
  215. createWrapper({
  216. activity: [
  217. {
  218. type: 'set_resolved_in_commit',
  219. id: '123',
  220. data: {
  221. commit: {
  222. id: 'komal-commit',
  223. repository: {},
  224. releases: [
  225. {
  226. dateCreated: '2022-05-01',
  227. dateReleased: '2022-05-02',
  228. version: 'random',
  229. },
  230. {
  231. dateCreated: '2022-06-01',
  232. dateReleased: '2022-06-02',
  233. version: 'newest',
  234. },
  235. {
  236. dateCreated: '2021-08-03',
  237. dateReleased: '2021-08-03',
  238. version: 'oldest-release',
  239. },
  240. {
  241. dateCreated: '2022-04-21',
  242. dateReleased: '2022-04-21',
  243. version: 'randomTwo',
  244. },
  245. ],
  246. },
  247. },
  248. user: TestStubs.User(),
  249. },
  250. ],
  251. });
  252. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  253. 'Foo Bar marked this issue as resolved in komal-commit This commit was released in oldest-release and 3 others'
  254. );
  255. });
  256. it('requests assignees that are not in the team store', async function () {
  257. const team = TestStubs.Team({id: '123', name: 'workflow'});
  258. const teamRequest = MockApiClient.addMockResponse({
  259. url: `/organizations/org-slug/teams/`,
  260. body: [team],
  261. });
  262. createWrapper({
  263. activity: [
  264. {
  265. id: '123',
  266. user: null,
  267. type: 'assigned',
  268. data: {
  269. assignee: team.id,
  270. assigneeEmail: null,
  271. assigneeType: 'team',
  272. },
  273. dateCreated: '2021-10-28T13:40:10.634821Z',
  274. },
  275. ],
  276. });
  277. await waitFor(() => expect(teamRequest).toHaveBeenCalledTimes(1));
  278. expect(
  279. await screen.findByText(`assigned this issue to #${team.slug}`)
  280. ).toBeInTheDocument();
  281. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  282. /Sentry assigned this issue to #team-slug/
  283. );
  284. });
  285. describe('Delete', function () {
  286. let deleteMock;
  287. beforeEach(function () {
  288. deleteMock = MockApiClient.addMockResponse({
  289. url: '/issues/1337/comments/note-1/',
  290. method: 'DELETE',
  291. });
  292. ConfigStore.set('user', {id: '123', isSuperuser: true});
  293. });
  294. it('should do nothing if not present in GroupStore', async function () {
  295. createWrapper();
  296. renderGlobalModal();
  297. act(() => {
  298. // Remove note from group activity
  299. GroupStore.removeActivity('1337', 'note-1');
  300. });
  301. await userEvent.click(screen.getByRole('button', {name: 'Comment Actions'}));
  302. await userEvent.click(screen.getByRole('menuitemradio', {name: 'Remove'}));
  303. expect(
  304. screen.getByText('Are you sure you wish to delete this comment?')
  305. ).toBeInTheDocument();
  306. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  307. expect(deleteMock).not.toHaveBeenCalled();
  308. });
  309. it('should remove remove the item from the GroupStore make a DELETE API request', async function () {
  310. createWrapper();
  311. renderGlobalModal();
  312. await userEvent.click(screen.getByRole('button', {name: 'Comment Actions'}));
  313. await userEvent.click(screen.getByRole('menuitemradio', {name: 'Remove'}));
  314. expect(
  315. screen.getByText('Are you sure you wish to delete this comment?')
  316. ).toBeInTheDocument();
  317. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  318. expect(deleteMock).toHaveBeenCalledTimes(1);
  319. });
  320. });
  321. it('renders ignored', function () {
  322. createWrapper({
  323. activity: [
  324. {
  325. id: '123',
  326. type: GroupActivityType.SET_IGNORED,
  327. data: {
  328. ignoreUntilEscalating: true,
  329. },
  330. user: TestStubs.User(),
  331. dateCreated,
  332. },
  333. ],
  334. });
  335. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  336. 'Foo Bar ignored this issue'
  337. );
  338. });
  339. it('renders archived until escalating if org has `escalating-issues` feature', function () {
  340. createWrapper({
  341. activity: [
  342. {
  343. id: '123',
  344. type: GroupActivityType.SET_IGNORED,
  345. data: {
  346. ignoreUntilEscalating: true,
  347. },
  348. user: TestStubs.User(),
  349. dateCreated,
  350. },
  351. ],
  352. organization: {features: ['escalating-issues']},
  353. });
  354. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  355. 'Foo Bar archived this issue until it escalates'
  356. );
  357. });
  358. it('renders escalating with forecast and plural events if org has `escalating-issues` feature', function () {
  359. createWrapper({
  360. activity: [
  361. {
  362. id: '123',
  363. type: GroupActivityType.SET_UNRESOLVED,
  364. data: {
  365. forecast: 200,
  366. },
  367. user: null,
  368. dateCreated,
  369. },
  370. {
  371. id: '124',
  372. type: GroupActivityType.SET_ESCALATING,
  373. data: {
  374. forecast: 400,
  375. },
  376. user: null,
  377. dateCreated: '2021-10-05T15:31:38.950115Z',
  378. },
  379. ],
  380. organization: {features: ['escalating-issues']},
  381. });
  382. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  383. 'Sentry flagged this issue as escalating because over 400 events happened in an hour'
  384. );
  385. expect(screen.getAllByTestId('activity-item').at(-2)).toHaveTextContent(
  386. 'Sentry flagged this issue as escalating because over 200 events happened in an hour'
  387. );
  388. });
  389. it('renders escalating with forecast and singular event if org has `escalating-issues` feature', function () {
  390. createWrapper({
  391. activity: [
  392. {
  393. id: '123',
  394. type: GroupActivityType.SET_UNRESOLVED,
  395. data: {
  396. forecast: 1,
  397. },
  398. user: null,
  399. dateCreated,
  400. },
  401. ],
  402. organization: {features: ['escalating-issues']},
  403. });
  404. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  405. 'Sentry flagged this issue as escalating because over 1 event happened in an hour'
  406. );
  407. });
  408. it('renders ignored until it happens x times in time window', function () {
  409. createWrapper({
  410. activity: [
  411. {
  412. id: '123',
  413. type: GroupActivityType.SET_IGNORED,
  414. data: {
  415. ignoreCount: 400,
  416. ignoreWindow: 1,
  417. },
  418. user: TestStubs.User(),
  419. dateCreated,
  420. },
  421. ],
  422. });
  423. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  424. 'Foo Bar ignored this issue until it happens 400 time(s) in 1 minute'
  425. );
  426. });
  427. });