groupActivity.spec.tsx 21 KB

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