groupActivity.spec.tsx 21 KB

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