actions.spec.jsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. import {selectDropdownMenuItem} from 'sentry-test/dropdownMenu';
  2. import {mountWithTheme} from 'sentry-test/enzyme';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {mountGlobalModal} from 'sentry-test/modal';
  5. import {selectByLabel} from 'sentry-test/select-new';
  6. import GroupStore from 'sentry/stores/groupStore';
  7. import SelectedGroupStore from 'sentry/stores/selectedGroupStore';
  8. import {IssueListActions} from 'sentry/views/issueList/actions';
  9. describe('IssueListActions', function () {
  10. let actions;
  11. let actionsWrapper;
  12. let wrapper;
  13. afterEach(() => {
  14. jest.restoreAllMocks();
  15. });
  16. describe('Bulk', function () {
  17. describe('Total results greater than bulk limit', function () {
  18. beforeAll(function () {
  19. const {routerContext, org} = initializeOrg();
  20. SelectedGroupStore.records = {};
  21. SelectedGroupStore.add(['1', '2', '3']);
  22. wrapper = mountWithTheme(
  23. <IssueListActions
  24. api={new MockApiClient()}
  25. allResultsVisible={false}
  26. query=""
  27. queryCount={1500}
  28. organization={org}
  29. projectId="project-slug"
  30. selection={{
  31. projects: [1],
  32. environments: [],
  33. datetime: {start: null, end: null, period: null, utc: true},
  34. }}
  35. groupIds={['1', '2', '3']}
  36. onRealtimeChange={function () {}}
  37. onSelectStatsPeriod={function () {}}
  38. realtimeActive={false}
  39. statsPeriod="24h"
  40. />,
  41. routerContext
  42. );
  43. });
  44. afterAll(() => {
  45. wrapper.unmount();
  46. });
  47. it('after checking "Select all" checkbox, displays bulk select message', async function () {
  48. wrapper.find('ActionsCheckbox Checkbox').simulate('change');
  49. expect(wrapper.find('SelectAllNotice')).toSnapshot();
  50. });
  51. it('can bulk select', function () {
  52. wrapper.find('SelectAllNotice').find('a').simulate('click');
  53. expect(wrapper.find('SelectAllNotice')).toSnapshot();
  54. });
  55. it('bulk resolves', async function () {
  56. const apiMock = MockApiClient.addMockResponse({
  57. url: '/organizations/org-slug/issues/',
  58. method: 'PUT',
  59. });
  60. wrapper.find('ResolveActions ResolveButton').simulate('click');
  61. const modal = await mountGlobalModal();
  62. expect(modal.find('Modal')).toSnapshot();
  63. modal.find('Button[priority="primary"]').simulate('click');
  64. expect(apiMock).toHaveBeenCalledWith(
  65. expect.anything(),
  66. expect.objectContaining({
  67. query: {
  68. project: [1],
  69. },
  70. data: {status: 'resolved'},
  71. })
  72. );
  73. await tick();
  74. wrapper.update();
  75. });
  76. });
  77. describe('Total results less than bulk limit', function () {
  78. beforeAll(function () {
  79. SelectedGroupStore.records = {};
  80. SelectedGroupStore.add(['1', '2', '3']);
  81. wrapper = mountWithTheme(
  82. <IssueListActions
  83. api={new MockApiClient()}
  84. allResultsVisible={false}
  85. query=""
  86. queryCount={600}
  87. organization={TestStubs.routerContext().context.organization}
  88. projectId="1"
  89. selection={{
  90. projects: [1],
  91. environments: [],
  92. datetime: {start: null, end: null, period: null, utc: true},
  93. }}
  94. groupIds={['1', '2', '3']}
  95. onRealtimeChange={function () {}}
  96. onSelectStatsPeriod={function () {}}
  97. realtimeActive={false}
  98. statsPeriod="24h"
  99. />,
  100. TestStubs.routerContext()
  101. );
  102. });
  103. afterAll(() => {
  104. wrapper.unmount();
  105. });
  106. it('after checking "Select all" checkbox, displays bulk select message', async function () {
  107. wrapper.find('ActionsCheckbox Checkbox').simulate('change');
  108. expect(wrapper.find('SelectAllNotice')).toSnapshot();
  109. });
  110. it('can bulk select', function () {
  111. wrapper.find('SelectAllNotice').find('a').simulate('click');
  112. expect(wrapper.find('SelectAllNotice')).toSnapshot();
  113. });
  114. it('bulk resolves', async function () {
  115. const apiMock = MockApiClient.addMockResponse({
  116. url: '/organizations/org-slug/issues/',
  117. method: 'PUT',
  118. });
  119. wrapper.find('ResolveActions ResolveButton').simulate('click');
  120. const modal = await mountGlobalModal();
  121. expect(modal.find('Modal')).toSnapshot();
  122. modal.find('Button[priority="primary"]').simulate('click');
  123. expect(apiMock).toHaveBeenCalledWith(
  124. expect.anything(),
  125. expect.objectContaining({
  126. query: {
  127. project: [1],
  128. },
  129. data: {status: 'resolved'},
  130. })
  131. );
  132. await tick();
  133. wrapper.update();
  134. });
  135. });
  136. describe('Selected on page', function () {
  137. beforeAll(function () {
  138. SelectedGroupStore.records = {};
  139. SelectedGroupStore.add(['1', '2', '3']);
  140. wrapper = mountWithTheme(
  141. <IssueListActions
  142. api={new MockApiClient()}
  143. allResultsVisible
  144. query=""
  145. queryCount={15}
  146. organization={TestStubs.routerContext().context.organization}
  147. projectId="1"
  148. selection={{
  149. projects: [1],
  150. environments: [],
  151. datetime: {start: null, end: null, period: null, utc: true},
  152. }}
  153. groupIds={['1', '2', '3', '6', '9']}
  154. onRealtimeChange={function () {}}
  155. onSelectStatsPeriod={function () {}}
  156. realtimeActive={false}
  157. statsPeriod="24h"
  158. />,
  159. TestStubs.routerContext()
  160. );
  161. });
  162. afterAll(() => {
  163. wrapper.unmount();
  164. });
  165. it('resolves selected items', function () {
  166. const apiMock = MockApiClient.addMockResponse({
  167. url: '/organizations/org-slug/issues/',
  168. method: 'PUT',
  169. });
  170. jest
  171. .spyOn(SelectedGroupStore, 'getSelectedIds')
  172. .mockImplementation(() => new Set(['3', '6', '9']));
  173. wrapper
  174. .find('IssueListActions')
  175. .setState({allInQuerySelected: false, anySelected: true});
  176. wrapper.find('ResolveActions ResolveButton').first().simulate('click');
  177. expect(apiMock).toHaveBeenCalledWith(
  178. expect.anything(),
  179. expect.objectContaining({
  180. query: {
  181. id: ['3', '6', '9'],
  182. project: [1],
  183. },
  184. data: {status: 'resolved'},
  185. })
  186. );
  187. });
  188. it('ignores selected items', async function () {
  189. const apiMock = MockApiClient.addMockResponse({
  190. url: '/organizations/org-slug/issues/',
  191. method: 'PUT',
  192. });
  193. jest
  194. .spyOn(SelectedGroupStore, 'getSelectedIds')
  195. .mockImplementation(() => new Set(['1']));
  196. wrapper
  197. .find('IssueListActions')
  198. .setState({allInQuerySelected: false, anySelected: true});
  199. await selectDropdownMenuItem({
  200. wrapper,
  201. specifiers: {prefix: 'IgnoreActions'},
  202. triggerSelector: 'DropdownTrigger',
  203. itemKey: ['until-affect', 'until-affect-custom'],
  204. });
  205. const modal = await mountGlobalModal();
  206. modal
  207. .find('CustomIgnoreCountModal input[label="Number of users"]')
  208. .simulate('change', {target: {value: 300}});
  209. selectByLabel(modal, 'per week', {
  210. name: 'window',
  211. });
  212. modal.find('Button[priority="primary"]').simulate('click');
  213. expect(apiMock).toHaveBeenCalledWith(
  214. expect.anything(),
  215. expect.objectContaining({
  216. query: {
  217. id: ['1'],
  218. project: [1],
  219. },
  220. data: {
  221. status: 'ignored',
  222. statusDetails: {
  223. ignoreUserCount: 300,
  224. ignoreUserWindow: 10080,
  225. },
  226. },
  227. })
  228. );
  229. });
  230. });
  231. });
  232. describe('actionSelectedGroups()', function () {
  233. beforeEach(function () {
  234. jest.spyOn(SelectedGroupStore, 'deselectAll');
  235. actionsWrapper = mountWithTheme(
  236. <IssueListActions
  237. api={new MockApiClient()}
  238. query=""
  239. organization={TestStubs.routerContext().context.organization}
  240. projectId="1"
  241. selection={{
  242. projects: [1],
  243. environments: [],
  244. datetime: {start: null, end: null, period: null, utc: true},
  245. }}
  246. groupIds={['1', '2', '3']}
  247. onRealtimeChange={function () {}}
  248. onSelectStatsPeriod={function () {}}
  249. realtimeActive={false}
  250. statsPeriod="24h"
  251. />
  252. );
  253. actions = actionsWrapper.instance();
  254. });
  255. afterEach(() => {
  256. actionsWrapper.unmount();
  257. });
  258. describe('for all items', function () {
  259. it("should invoke the callback with 'undefined' and deselect all", function () {
  260. const callback = jest.fn();
  261. actions.state.allInQuerySelected = true;
  262. actions.actionSelectedGroups(callback);
  263. expect(callback).toHaveBeenCalledWith(undefined);
  264. expect(callback).toHaveBeenCalledTimes(1);
  265. expect(SelectedGroupStore.deselectAll).toHaveBeenCalledTimes(1);
  266. // all selected is reset
  267. expect(actions.state.allInQuerySelected).toBe(false);
  268. });
  269. });
  270. describe('for page-selected items', function () {
  271. it('should invoke the callback with an array of selected items and deselect all', function () {
  272. jest
  273. .spyOn(SelectedGroupStore, 'getSelectedIds')
  274. .mockImplementation(() => new Set(['1', '2', '3']));
  275. actions.state.allInQuerySelected = false;
  276. const callback = jest.fn();
  277. actions.actionSelectedGroups(callback);
  278. expect(callback).toHaveBeenCalledWith(['1', '2', '3']);
  279. expect(callback).toHaveBeenCalledTimes(1);
  280. expect(SelectedGroupStore.deselectAll).toHaveBeenCalledTimes(1);
  281. });
  282. });
  283. });
  284. describe('multiple groups from different project', function () {
  285. beforeEach(function () {
  286. jest
  287. .spyOn(SelectedGroupStore, 'getSelectedIds')
  288. .mockImplementation(() => new Set(['1', '2', '3']));
  289. wrapper = mountWithTheme(
  290. <IssueListActions
  291. api={new MockApiClient()}
  292. query=""
  293. organization={TestStubs.routerContext().context.organization}
  294. groupIds={['1', '2', '3']}
  295. selection={{
  296. projects: [],
  297. environments: [],
  298. datetime: {start: null, end: null, period: null, utc: true},
  299. }}
  300. onRealtimeChange={function () {}}
  301. onSelectStatsPeriod={function () {}}
  302. realtimeActive={false}
  303. statsPeriod="24h"
  304. />,
  305. TestStubs.routerContext()
  306. );
  307. });
  308. afterEach(() => {
  309. wrapper.unmount();
  310. });
  311. it('should disable resolve dropdown but not resolve action', function () {
  312. const resolve = wrapper.find('ResolveActions').first();
  313. expect(resolve.props().disabled).toBe(false);
  314. expect(resolve.props().disableDropdown).toBe(true);
  315. });
  316. it('should disable merge button', function () {
  317. expect(
  318. wrapper.find('button[aria-label="Merge Selected Issues"]').props()[
  319. 'aria-disabled'
  320. ]
  321. ).toBe(true);
  322. });
  323. });
  324. describe('mark reviewed', function () {
  325. let issuesApiMock;
  326. beforeEach(async () => {
  327. SelectedGroupStore.records = {};
  328. const {organization} = TestStubs.routerContext().context;
  329. wrapper = mountWithTheme(
  330. <IssueListActions
  331. api={new MockApiClient()}
  332. query=""
  333. organization={organization}
  334. groupIds={['1', '2', '3']}
  335. selection={{
  336. projects: [],
  337. environments: [],
  338. datetime: {start: null, end: null, period: null, utc: true},
  339. }}
  340. onRealtimeChange={function () {}}
  341. onSelectStatsPeriod={function () {}}
  342. realtimeActive={false}
  343. statsPeriod="24h"
  344. queryCount={100}
  345. displayCount="3 of 3"
  346. />,
  347. TestStubs.routerContext()
  348. );
  349. MockApiClient.addMockResponse({
  350. url: '/organizations/org-slug/projects/',
  351. body: [TestStubs.Project({slug: 'earth', platform: 'javascript'})],
  352. });
  353. issuesApiMock = MockApiClient.addMockResponse({
  354. url: '/organizations/org-slug/issues/',
  355. method: 'PUT',
  356. });
  357. });
  358. afterEach(() => {
  359. wrapper.unmount();
  360. });
  361. it('acknowledges group', async function () {
  362. wrapper.find('IssueListActions').setState({anySelected: true});
  363. SelectedGroupStore.add(['1', '2', '3']);
  364. SelectedGroupStore.toggleSelectAll();
  365. const inbox = {
  366. date_added: '2020-11-24T13:17:42.248751Z',
  367. reason: 0,
  368. reason_details: null,
  369. };
  370. GroupStore.loadInitialData([
  371. TestStubs.Group({id: '1', inbox}),
  372. TestStubs.Group({id: '2', inbox}),
  373. TestStubs.Group({id: '2', inbox}),
  374. ]);
  375. await tick();
  376. wrapper.find('button[aria-label="Mark Reviewed"]').simulate('click');
  377. expect(issuesApiMock).toHaveBeenCalledWith(
  378. expect.anything(),
  379. expect.objectContaining({
  380. data: {inbox: false},
  381. })
  382. );
  383. });
  384. it('mark reviewed disabled for group that is already reviewed', async function () {
  385. wrapper.find('IssueListActions').setState({anySelected: true});
  386. SelectedGroupStore.add(['1']);
  387. SelectedGroupStore.toggleSelectAll();
  388. GroupStore.loadInitialData([TestStubs.Group({id: '1', inbox: null})]);
  389. await tick();
  390. expect(
  391. wrapper.find('button[aria-label="Mark Reviewed"]').props()['aria-disabled']
  392. ).toBe(true);
  393. });
  394. });
  395. });