groupActivity.spec.tsx 21 KB

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