groupActivity.spec.tsx 20 KB

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