groupActivity.spec.tsx 20 KB

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