groupActivity.spec.tsx 21 KB

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