groupActivity.spec.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. import {Organization} from 'sentry-fixture/organization';
  2. import {Repository} from 'sentry-fixture/repository';
  3. import {Team} from 'sentry-fixture/team';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {
  6. act,
  7. render,
  8. renderGlobalModal,
  9. screen,
  10. userEvent,
  11. waitFor,
  12. } from 'sentry-test/reactTestingLibrary';
  13. import ConfigStore from 'sentry/stores/configStore';
  14. import GroupStore from 'sentry/stores/groupStore';
  15. import OrganizationStore from 'sentry/stores/organizationStore';
  16. import ProjectsStore from 'sentry/stores/projectsStore';
  17. import TeamStore from 'sentry/stores/teamStore';
  18. import {Group, GroupActivityType, Organization as TOrganization} from 'sentry/types';
  19. import {GroupActivity} from 'sentry/views/issueDetails/groupActivity';
  20. describe('GroupActivity', function () {
  21. let project;
  22. const dateCreated = '2021-10-01T15:31:38.950115Z';
  23. beforeEach(function () {
  24. project = TestStubs.Project();
  25. ProjectsStore.loadInitialData([project]);
  26. ConfigStore.init();
  27. ConfigStore.set('user', TestStubs.User({id: '123'}));
  28. GroupStore.init();
  29. });
  30. afterEach(() => {
  31. MockApiClient.clearMockResponses();
  32. jest.clearAllMocks();
  33. });
  34. function createWrapper({
  35. activity,
  36. organization: additionalOrg,
  37. }: {
  38. activity?: Group['activity'];
  39. organization?: TOrganization;
  40. } = {}) {
  41. const group = TestStubs.Group({
  42. id: '1337',
  43. activity: activity ?? [
  44. {type: 'note', id: 'note-1', data: {text: 'Test Note'}, user: TestStubs.User()},
  45. ],
  46. project,
  47. });
  48. const {organization, routerContext, routerProps} = initializeOrg({
  49. organization: additionalOrg,
  50. });
  51. GroupStore.add([group]);
  52. TeamStore.loadInitialData([Team({id: '999', slug: 'no-team'})]);
  53. OrganizationStore.onUpdate(organization, {replace: true});
  54. return render(
  55. <GroupActivity
  56. {...routerProps}
  57. api={new MockApiClient()}
  58. params={{orgId: 'org-slug'}}
  59. group={group}
  60. organization={organization}
  61. />,
  62. {context: routerContext}
  63. );
  64. }
  65. it('renders a NoteInput', function () {
  66. createWrapper();
  67. expect(screen.getByTestId('activity-note-body')).toBeInTheDocument();
  68. });
  69. it('renders a marked reviewed activity', function () {
  70. const user = TestStubs.User({name: 'Samwise'});
  71. createWrapper({
  72. activity: [
  73. {
  74. type: GroupActivityType.MARK_REVIEWED,
  75. id: 'reviewed-1',
  76. dateCreated: '',
  77. project: TestStubs.Project(),
  78. data: {},
  79. user,
  80. },
  81. ],
  82. });
  83. expect(screen.getByText('marked this issue as reviewed')).toBeInTheDocument();
  84. expect(screen.getByText(user.name)).toBeInTheDocument();
  85. });
  86. it('renders a pr activity', function () {
  87. const user = TestStubs.User({name: 'Test User'});
  88. const repository = Repository();
  89. createWrapper({
  90. activity: [
  91. {
  92. dateCreated: '',
  93. project: TestStubs.Project(),
  94. type: GroupActivityType.SET_RESOLVED_IN_PULL_REQUEST,
  95. id: 'pr-1',
  96. data: {
  97. pullRequest: {
  98. externalUrl: '',
  99. id: '',
  100. title: '',
  101. repository,
  102. },
  103. },
  104. user,
  105. },
  106. ],
  107. });
  108. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  109. 'Test User has created a PR for this issue:'
  110. );
  111. });
  112. it('renders a assigned to self activity', function () {
  113. const user = TestStubs.User({id: '123', name: 'Mark'});
  114. createWrapper({
  115. activity: [
  116. {
  117. data: {
  118. assignee: user.id,
  119. assigneeEmail: user.email,
  120. assigneeType: 'user',
  121. user,
  122. },
  123. user,
  124. dateCreated: '2021-10-01T15:31:38.950115Z',
  125. id: '117',
  126. project: TestStubs.Project(),
  127. type: GroupActivityType.ASSIGNED,
  128. },
  129. ],
  130. });
  131. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  132. /Mark assigned this issue to themselves/
  133. );
  134. });
  135. it('renders an assigned via codeowners activity', function () {
  136. createWrapper({
  137. activity: [
  138. {
  139. data: {
  140. assignee: '123',
  141. assigneeEmail: 'anotheruser@sentry.io',
  142. assigneeType: 'user',
  143. integration: 'codeowners',
  144. rule: 'path:something/*.py #workflow',
  145. user: TestStubs.User(),
  146. },
  147. project: TestStubs.Project(),
  148. dateCreated: '2021-10-01T15:31:38.950115Z',
  149. id: '117',
  150. type: GroupActivityType.ASSIGNED,
  151. user: null,
  152. },
  153. ],
  154. });
  155. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  156. /Sentry auto-assigned this issue to anotheruser@sentry.io/
  157. );
  158. });
  159. it('renders an assigned via slack activity', function () {
  160. const user = TestStubs.User({id: '301', name: 'Mark'});
  161. createWrapper({
  162. activity: [
  163. {
  164. data: {
  165. assignee: '123',
  166. assigneeEmail: 'anotheruser@sentry.io',
  167. assigneeType: 'user',
  168. integration: 'slack',
  169. user: TestStubs.User(),
  170. },
  171. project: TestStubs.Project(),
  172. dateCreated: '2021-10-01T15:31:38.950115Z',
  173. id: '117',
  174. type: GroupActivityType.ASSIGNED,
  175. user,
  176. },
  177. ],
  178. });
  179. const item = screen.getAllByTestId('activity-item').at(-1);
  180. expect(item).toHaveTextContent(/Mark assigned this issue to anotheruser@sentry.io/);
  181. expect(item).toHaveTextContent(/Assigned via Slack/);
  182. });
  183. it('resolved in commit with no releases', function () {
  184. createWrapper({
  185. activity: [
  186. {
  187. type: GroupActivityType.SET_RESOLVED_IN_COMMIT,
  188. id: '123',
  189. project: TestStubs.Project(),
  190. dateCreated: '',
  191. data: {
  192. commit: {
  193. dateCreated: '',
  194. message: '',
  195. id: 'komal-commit',
  196. repository: Repository(),
  197. releases: [],
  198. },
  199. },
  200. user: TestStubs.User(),
  201. },
  202. ],
  203. });
  204. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  205. 'Foo Bar marked this issue as resolved in komal-commit'
  206. );
  207. });
  208. it('resolved in commit with one release', function () {
  209. createWrapper({
  210. activity: [
  211. {
  212. type: GroupActivityType.SET_RESOLVED_IN_COMMIT,
  213. id: '123',
  214. project: TestStubs.Project(),
  215. dateCreated: '',
  216. data: {
  217. commit: {
  218. id: 'komal-commit',
  219. dateCreated: '',
  220. message: '',
  221. repository: Repository(),
  222. releases: [
  223. TestStubs.Release({
  224. dateCreated: '2022-05-01',
  225. dateReleased: '2022-05-02',
  226. version: 'random',
  227. }),
  228. ],
  229. },
  230. },
  231. user: TestStubs.User(),
  232. },
  233. ],
  234. });
  235. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  236. 'Foo Bar marked this issue as resolved in komal-commit This commit was released in random'
  237. );
  238. });
  239. it('resolved in commit with multiple releases', function () {
  240. createWrapper({
  241. activity: [
  242. {
  243. type: GroupActivityType.SET_RESOLVED_IN_COMMIT,
  244. id: '123',
  245. project: TestStubs.Project(),
  246. dateCreated: '',
  247. data: {
  248. commit: {
  249. id: 'komal-commit',
  250. dateCreated: '',
  251. message: '',
  252. repository: Repository(),
  253. releases: [
  254. TestStubs.Release({
  255. dateCreated: '2022-05-01',
  256. dateReleased: '2022-05-02',
  257. version: 'random',
  258. }),
  259. TestStubs.Release({
  260. dateCreated: '2022-06-01',
  261. dateReleased: '2022-06-02',
  262. version: 'newest',
  263. }),
  264. TestStubs.Release({
  265. dateCreated: '2021-08-03',
  266. dateReleased: '2021-08-03',
  267. version: 'oldest-release',
  268. }),
  269. TestStubs.Release({
  270. dateCreated: '2022-04-21',
  271. dateReleased: '2022-04-21',
  272. version: 'randomTwo',
  273. }),
  274. ],
  275. },
  276. },
  277. user: TestStubs.User(),
  278. },
  279. ],
  280. });
  281. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  282. 'Foo Bar marked this issue as resolved in komal-commit This commit was released in oldest-release and 3 others'
  283. );
  284. });
  285. it('requests assignees that are not in the team store', async function () {
  286. const team = Team({id: '123', name: 'workflow'});
  287. const teamRequest = MockApiClient.addMockResponse({
  288. url: `/organizations/org-slug/teams/`,
  289. body: [team],
  290. });
  291. createWrapper({
  292. activity: [
  293. {
  294. id: '123',
  295. user: null,
  296. type: GroupActivityType.ASSIGNED,
  297. project: TestStubs.Project(),
  298. data: {
  299. assignee: team.id,
  300. assigneeType: 'team',
  301. user: TestStubs.User(),
  302. },
  303. dateCreated: '2021-10-28T13:40:10.634821Z',
  304. },
  305. ],
  306. });
  307. await waitFor(() => expect(teamRequest).toHaveBeenCalledTimes(1));
  308. expect(
  309. await screen.findByText(`assigned this issue to #${team.slug}`)
  310. ).toBeInTheDocument();
  311. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  312. /Sentry assigned this issue to #team-slug/
  313. );
  314. });
  315. describe('Delete', function () {
  316. let deleteMock;
  317. beforeEach(function () {
  318. deleteMock = MockApiClient.addMockResponse({
  319. url: '/organizations/org-slug/issues/1337/comments/note-1/',
  320. method: 'DELETE',
  321. });
  322. ConfigStore.set('user', TestStubs.User({id: '123', isSuperuser: true}));
  323. });
  324. it('should do nothing if not present in GroupStore', async function () {
  325. createWrapper();
  326. renderGlobalModal();
  327. act(() => {
  328. // Remove note from group activity
  329. GroupStore.removeActivity('1337', 'note-1');
  330. });
  331. await userEvent.click(screen.getByRole('button', {name: 'Comment Actions'}));
  332. await userEvent.click(screen.getByRole('menuitemradio', {name: 'Remove'}));
  333. expect(
  334. screen.getByText('Are you sure you wish to delete this comment?')
  335. ).toBeInTheDocument();
  336. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  337. expect(deleteMock).not.toHaveBeenCalled();
  338. });
  339. it('should remove remove the item from the GroupStore make a DELETE API request', async function () {
  340. createWrapper();
  341. renderGlobalModal();
  342. await userEvent.click(screen.getByRole('button', {name: 'Comment Actions'}));
  343. await userEvent.click(screen.getByRole('menuitemradio', {name: 'Remove'}));
  344. expect(
  345. screen.getByText('Are you sure you wish to delete this comment?')
  346. ).toBeInTheDocument();
  347. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  348. expect(deleteMock).toHaveBeenCalledTimes(1);
  349. });
  350. });
  351. it('renders ignored', function () {
  352. createWrapper({
  353. activity: [
  354. {
  355. id: '123',
  356. type: GroupActivityType.SET_IGNORED,
  357. project: TestStubs.Project(),
  358. data: {
  359. ignoreUntilEscalating: true,
  360. },
  361. user: TestStubs.User(),
  362. dateCreated,
  363. },
  364. ],
  365. });
  366. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  367. 'Foo Bar ignored this issue'
  368. );
  369. });
  370. it('renders archived until escalating if org has `escalating-issues` feature', function () {
  371. createWrapper({
  372. activity: [
  373. {
  374. id: '123',
  375. type: GroupActivityType.SET_IGNORED,
  376. project: TestStubs.Project(),
  377. data: {
  378. ignoreUntilEscalating: true,
  379. },
  380. user: TestStubs.User(),
  381. dateCreated,
  382. },
  383. ],
  384. organization: Organization({features: ['escalating-issues']}),
  385. });
  386. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  387. 'Foo Bar archived this issue until it escalates'
  388. );
  389. });
  390. it('renders escalating with forecast and plural events if org has `escalating-issues` feature', function () {
  391. createWrapper({
  392. activity: [
  393. {
  394. id: '123',
  395. type: GroupActivityType.SET_UNRESOLVED,
  396. project: TestStubs.Project(),
  397. data: {
  398. forecast: 200,
  399. },
  400. user: null,
  401. dateCreated,
  402. },
  403. {
  404. id: '124',
  405. type: GroupActivityType.SET_ESCALATING,
  406. project: TestStubs.Project(),
  407. data: {
  408. forecast: 400,
  409. },
  410. user: null,
  411. dateCreated: '2021-10-05T15:31:38.950115Z',
  412. },
  413. ],
  414. organization: Organization({features: ['escalating-issues']}),
  415. });
  416. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  417. 'Sentry flagged this issue as escalating because over 400 events happened in an hour'
  418. );
  419. expect(screen.getAllByTestId('activity-item').at(-2)).toHaveTextContent(
  420. 'Sentry flagged this issue as escalating because over 200 events happened in an hour'
  421. );
  422. });
  423. it('renders escalating with forecast and singular event if org has `escalating-issues` feature', function () {
  424. createWrapper({
  425. activity: [
  426. {
  427. id: '123',
  428. type: GroupActivityType.SET_UNRESOLVED,
  429. project: TestStubs.Project(),
  430. data: {
  431. forecast: 1,
  432. },
  433. user: null,
  434. dateCreated,
  435. },
  436. ],
  437. organization: Organization({features: ['escalating-issues']}),
  438. });
  439. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  440. 'Sentry flagged this issue as escalating because over 1 event happened in an hour'
  441. );
  442. });
  443. it('renders ignored until it happens x times in time window', function () {
  444. createWrapper({
  445. activity: [
  446. {
  447. id: '123',
  448. type: GroupActivityType.SET_IGNORED,
  449. project: TestStubs.Project(),
  450. data: {
  451. ignoreCount: 400,
  452. ignoreWindow: 1,
  453. },
  454. user: TestStubs.User(),
  455. dateCreated,
  456. },
  457. ],
  458. });
  459. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  460. 'Foo Bar ignored this issue until it happens 400 time(s) in 1 minute'
  461. );
  462. });
  463. it('renders escalating since it happened x times in time window', function () {
  464. createWrapper({
  465. activity: [
  466. {
  467. id: '123',
  468. type: GroupActivityType.SET_ESCALATING,
  469. project: TestStubs.Project(),
  470. data: {
  471. expired_snooze: {
  472. count: 400,
  473. window: 1,
  474. until: null,
  475. user_count: null,
  476. user_window: null,
  477. },
  478. },
  479. dateCreated,
  480. },
  481. ],
  482. });
  483. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  484. 'Sentry flagged this issue as escalating because 400 events happened in 1 minute'
  485. );
  486. });
  487. it('renders escalating since x users were affected in time window', function () {
  488. createWrapper({
  489. activity: [
  490. {
  491. id: '123',
  492. type: GroupActivityType.SET_ESCALATING,
  493. project: TestStubs.Project(),
  494. data: {
  495. expired_snooze: {
  496. user_count: 1,
  497. user_window: 1,
  498. until: null,
  499. count: null,
  500. window: null,
  501. },
  502. },
  503. dateCreated,
  504. },
  505. ],
  506. });
  507. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  508. 'Sentry flagged this issue as escalating because 1 user was affected in 1 minute'
  509. );
  510. });
  511. it('renders escalating since until date passed', function () {
  512. const date = new Date('2018-10-30');
  513. createWrapper({
  514. activity: [
  515. {
  516. id: '123',
  517. type: GroupActivityType.SET_ESCALATING,
  518. project: TestStubs.Project(),
  519. data: {
  520. expired_snooze: {
  521. until: date,
  522. user_count: null,
  523. user_window: null,
  524. count: null,
  525. window: null,
  526. },
  527. },
  528. dateCreated,
  529. },
  530. ],
  531. });
  532. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  533. 'Sentry flagged this issue as escalating because Oct 30, 2018 12:00 AM passed'
  534. );
  535. });
  536. it('renders archived forever', function () {
  537. createWrapper({
  538. activity: [
  539. {
  540. id: '123',
  541. type: GroupActivityType.SET_IGNORED,
  542. project: TestStubs.Project(),
  543. data: {},
  544. user: TestStubs.User(),
  545. dateCreated,
  546. },
  547. ],
  548. organization: Organization({features: ['escalating-issues']}),
  549. });
  550. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  551. 'Foo Bar archived this issue forever'
  552. );
  553. });
  554. it('renders resolved in release with semver information', function () {
  555. createWrapper({
  556. activity: [
  557. {
  558. id: '123',
  559. type: GroupActivityType.SET_RESOLVED_IN_RELEASE,
  560. project: TestStubs.Project(),
  561. data: {
  562. version: 'frontend@1.0.0',
  563. },
  564. user: TestStubs.User(),
  565. dateCreated,
  566. },
  567. ],
  568. });
  569. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  570. 'Foo Bar marked this issue as resolved in 1.0.0 (semver)'
  571. );
  572. });
  573. it('renders resolved in next release with semver information', function () {
  574. createWrapper({
  575. activity: [
  576. {
  577. id: '123',
  578. type: GroupActivityType.SET_RESOLVED_IN_RELEASE,
  579. project: TestStubs.Project(),
  580. data: {
  581. current_release_version: 'frontend@1.0.0',
  582. },
  583. user: TestStubs.User(),
  584. dateCreated,
  585. },
  586. ],
  587. });
  588. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  589. 'Foo Bar marked this issue as resolved in releases greater than 1.0.0 (semver)'
  590. );
  591. });
  592. describe('regression', function () {
  593. it('renders basic regression', function () {
  594. createWrapper({
  595. activity: [
  596. {
  597. id: '123',
  598. type: GroupActivityType.SET_REGRESSION,
  599. project: TestStubs.Project(),
  600. data: {},
  601. dateCreated,
  602. },
  603. ],
  604. });
  605. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  606. 'Sentry marked this issue as a regression'
  607. );
  608. });
  609. it('renders regression with version', function () {
  610. createWrapper({
  611. activity: [
  612. {
  613. id: '123',
  614. type: GroupActivityType.SET_REGRESSION,
  615. project: TestStubs.Project(),
  616. data: {
  617. version: 'frontend@1.0.0',
  618. },
  619. dateCreated,
  620. },
  621. ],
  622. });
  623. expect(screen.getAllByTestId('activity-item').at(-1)).toHaveTextContent(
  624. 'Sentry marked this issue as a regression in 1.0.0'
  625. );
  626. });
  627. it('renders regression with semver description', function () {
  628. createWrapper({
  629. activity: [
  630. {
  631. id: '123',
  632. type: GroupActivityType.SET_REGRESSION,
  633. project: TestStubs.Project(),
  634. data: {
  635. version: 'frontend@2.0.0',
  636. resolved_in_version: 'frontend@1.0.0',
  637. follows_semver: true,
  638. },
  639. dateCreated,
  640. },
  641. ],
  642. });
  643. const activity = screen.getAllByTestId('activity-item').at(-1);
  644. expect(activity).toHaveTextContent(
  645. 'Sentry marked this issue as a regression in 2.0.0'
  646. );
  647. expect(activity).toHaveTextContent(
  648. '2.0.0 is greater than or equal to 1.0.0 compared via semver'
  649. );
  650. });
  651. it('renders regression with non-semver description', function () {
  652. createWrapper({
  653. activity: [
  654. {
  655. id: '123',
  656. type: GroupActivityType.SET_REGRESSION,
  657. project: TestStubs.Project(),
  658. data: {
  659. version: 'frontend@abc1',
  660. resolved_in_version: 'frontend@abc2',
  661. follows_semver: false,
  662. },
  663. dateCreated,
  664. },
  665. ],
  666. });
  667. const activity = screen.getAllByTestId('activity-item').at(-1);
  668. expect(activity).toHaveTextContent(
  669. 'Sentry marked this issue as a regression in abc1'
  670. );
  671. expect(activity).toHaveTextContent(
  672. 'abc1 is greater than or equal to abc2 compared via release date'
  673. );
  674. });
  675. });
  676. });