teamKeyTransactionButton.spec.jsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import ProjectsStore from 'app/stores/projectsStore';
  3. import TeamStore from 'app/stores/teamStore';
  4. import EventView from 'app/utils/discover/eventView';
  5. import {MAX_TEAM_KEY_TRANSACTIONS} from 'app/utils/performance/constants';
  6. import TeamKeyTransactionButton from 'app/views/performance/transactionSummary/teamKeyTransactionButton';
  7. async function clickTeamKeyTransactionDropdown(wrapper) {
  8. wrapper.find('Button').simulate('click');
  9. await tick();
  10. wrapper.update();
  11. }
  12. describe('TeamKeyTransactionButton', function () {
  13. const organization = TestStubs.Organization({features: ['performance-view']});
  14. const teams = [
  15. TestStubs.Team({id: '1', slug: 'team1', name: 'Team 1'}),
  16. TestStubs.Team({id: '2', slug: 'team2', name: 'Team 2'}),
  17. ];
  18. const project = TestStubs.Project({teams});
  19. const eventView = new EventView({
  20. id: '1',
  21. name: 'my query',
  22. fields: [{field: 'count()'}],
  23. sorts: [{field: 'count', kind: 'desc'}],
  24. query: '',
  25. project: [project.id],
  26. start: '2019-10-01T00:00:00',
  27. end: '2019-10-02T00:00:00',
  28. statsPeriod: '14d',
  29. environment: [],
  30. });
  31. beforeEach(function () {
  32. MockApiClient.clearMockResponses();
  33. ProjectsStore.loadInitialData([project]);
  34. TeamStore.loadInitialData(teams);
  35. });
  36. it('fetches key transactions with project param', async function () {
  37. const getTeamKeyTransactionsMock = MockApiClient.addMockResponse(
  38. {
  39. method: 'GET',
  40. url: '/organizations/org-slug/key-transactions-list/',
  41. body: teams.map(({id}) => ({
  42. team: id,
  43. count: 1,
  44. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  45. })),
  46. },
  47. {
  48. predicate: (_, options) => {
  49. return (
  50. options.method === 'GET' &&
  51. options.query.project.length === 1 &&
  52. options.query.project[0] === project.id &&
  53. options.query.team.length === 1 &&
  54. options.query.team[0] === 'myteams'
  55. );
  56. },
  57. }
  58. );
  59. const wrapper = mountWithTheme(
  60. <TeamKeyTransactionButton
  61. eventView={eventView}
  62. organization={organization}
  63. transactionName="transaction"
  64. />
  65. );
  66. await tick();
  67. wrapper.update();
  68. expect(getTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  69. });
  70. it('renders with all teams checked', async function () {
  71. MockApiClient.addMockResponse({
  72. method: 'GET',
  73. url: '/organizations/org-slug/key-transactions-list/',
  74. body: teams.map(({id}) => ({
  75. team: id,
  76. count: 1,
  77. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  78. })),
  79. });
  80. const wrapper = mountWithTheme(
  81. <TeamKeyTransactionButton
  82. eventView={eventView}
  83. organization={organization}
  84. transactionName="transaction"
  85. />
  86. );
  87. await tick();
  88. wrapper.update();
  89. clickTeamKeyTransactionDropdown(wrapper);
  90. // header should show the checked state
  91. expect(wrapper.find('TitleButton').exists()).toBeTruthy();
  92. const header = wrapper.find('DropdownMenuHeader');
  93. expect(header.exists()).toBeTruthy();
  94. expect(header.find('CheckboxFancy').props().isChecked).toBeTruthy();
  95. expect(header.find('CheckboxFancy').props().isIndeterminate).toBeFalsy();
  96. // all teams should be checked
  97. const entries = wrapper.find('DropdownMenuItem');
  98. expect(entries.length).toBe(2);
  99. entries.forEach((entry, i) => {
  100. expect(entry.text()).toEqual(teams[i].slug);
  101. expect(entry.find('CheckboxFancy').props().isChecked).toBeTruthy();
  102. });
  103. });
  104. it('renders with some teams checked', async function () {
  105. MockApiClient.addMockResponse({
  106. method: 'GET',
  107. url: '/organizations/org-slug/key-transactions-list/',
  108. body: teams.map(({id}) => ({
  109. team: id,
  110. count: id === teams[0].id ? 1 : 0,
  111. keyed:
  112. id === teams[0].id
  113. ? [{project_id: String(project.id), transaction: 'transaction'}]
  114. : [],
  115. })),
  116. });
  117. const wrapper = mountWithTheme(
  118. <TeamKeyTransactionButton
  119. eventView={eventView}
  120. organization={organization}
  121. transactionName="transaction"
  122. />
  123. );
  124. await tick();
  125. wrapper.update();
  126. clickTeamKeyTransactionDropdown(wrapper);
  127. // header should show the indeterminate state
  128. const header = wrapper.find('DropdownMenuHeader');
  129. expect(header.exists()).toBeTruthy();
  130. expect(header.find('CheckboxFancy').props().isChecked).toBeFalsy();
  131. expect(header.find('CheckboxFancy').props().isIndeterminate).toBeTruthy();
  132. // only team 1 should be checked
  133. const entries = wrapper.find('DropdownMenuItem');
  134. expect(entries.length).toBe(2);
  135. entries.forEach((entry, i) => {
  136. expect(entry.text()).toEqual(teams[i].slug);
  137. });
  138. expect(entries.at(0).find('CheckboxFancy').props().isChecked).toBeTruthy();
  139. expect(entries.at(1).find('CheckboxFancy').props().isChecked).toBeFalsy();
  140. });
  141. it('renders with no teams checked', async function () {
  142. MockApiClient.addMockResponse({
  143. method: 'GET',
  144. url: '/organizations/org-slug/key-transactions-list/',
  145. body: teams.map(({id}) => ({
  146. team: id,
  147. count: 0,
  148. keyed: [],
  149. })),
  150. });
  151. const wrapper = mountWithTheme(
  152. <TeamKeyTransactionButton
  153. eventView={eventView}
  154. organization={organization}
  155. transactionName="transaction"
  156. />
  157. );
  158. await tick();
  159. wrapper.update();
  160. clickTeamKeyTransactionDropdown(wrapper);
  161. // header should show the unchecked state
  162. const header = wrapper.find('DropdownMenuHeader');
  163. expect(header.exists()).toBeTruthy();
  164. expect(header.find('CheckboxFancy').props().isChecked).toBeFalsy();
  165. expect(header.find('CheckboxFancy').props().isIndeterminate).toBeFalsy();
  166. // all teams should be unchecked
  167. const entries = wrapper.find('DropdownMenuItem');
  168. expect(entries.length).toBe(2);
  169. entries.forEach((entry, i) => {
  170. expect(entry.text()).toEqual(teams[i].slug);
  171. expect(entry.find('CheckboxFancy').props().isChecked).toBeFalsy();
  172. });
  173. });
  174. it('should be able to check one team', async function () {
  175. MockApiClient.addMockResponse({
  176. method: 'GET',
  177. url: '/organizations/org-slug/key-transactions-list/',
  178. body: teams.map(({id}) => ({
  179. team: id,
  180. count: 0,
  181. keyed: [],
  182. })),
  183. });
  184. const postTeamKeyTransactionsMock = MockApiClient.addMockResponse(
  185. {
  186. method: 'POST',
  187. url: '/organizations/org-slug/key-transactions/',
  188. body: [],
  189. },
  190. {
  191. predicate: (_, options) =>
  192. options.method === 'POST' &&
  193. options.query.project.length === 1 &&
  194. options.query.project[0] === project.id &&
  195. options.data.team.length === 1 &&
  196. options.data.team[0] === teams[0].id &&
  197. options.data.transaction === 'transaction',
  198. }
  199. );
  200. const wrapper = mountWithTheme(
  201. <TeamKeyTransactionButton
  202. eventView={eventView}
  203. organization={organization}
  204. transactionName="transaction"
  205. />
  206. );
  207. await tick();
  208. wrapper.update();
  209. clickTeamKeyTransactionDropdown(wrapper);
  210. wrapper.find('DropdownMenuItem CheckboxFancy').first().simulate('click');
  211. await tick();
  212. wrapper.update();
  213. const checkbox = wrapper.find('DropdownMenuItem CheckboxFancy').first();
  214. expect(checkbox.props().isChecked).toBeTruthy();
  215. expect(postTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  216. });
  217. it('should be able to uncheck one team', async function () {
  218. MockApiClient.addMockResponse({
  219. method: 'GET',
  220. url: '/organizations/org-slug/key-transactions-list/',
  221. body: teams.map(({id}) => ({
  222. team: id,
  223. count: 1,
  224. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  225. })),
  226. });
  227. const deleteTeamKeyTransactionsMock = MockApiClient.addMockResponse(
  228. {
  229. method: 'DELETE',
  230. url: '/organizations/org-slug/key-transactions/',
  231. body: [],
  232. },
  233. {
  234. predicate: (_, options) =>
  235. options.method === 'DELETE' &&
  236. options.query.project.length === 1 &&
  237. options.query.project[0] === project.id &&
  238. options.data.team.length === 1 &&
  239. options.data.team[0] === teams[0].id &&
  240. options.data.transaction === 'transaction',
  241. }
  242. );
  243. const wrapper = mountWithTheme(
  244. <TeamKeyTransactionButton
  245. eventView={eventView}
  246. organization={organization}
  247. transactionName="transaction"
  248. />
  249. );
  250. await tick();
  251. wrapper.update();
  252. clickTeamKeyTransactionDropdown(wrapper);
  253. wrapper.find('DropdownMenuItem CheckboxFancy').first().simulate('click');
  254. await tick();
  255. wrapper.update();
  256. const checkbox = wrapper.find('DropdownMenuItem CheckboxFancy').first();
  257. expect(checkbox.props().isChecked).toBeFalsy();
  258. expect(deleteTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  259. });
  260. it('should be able to check all with my teams', async function () {
  261. MockApiClient.addMockResponse({
  262. method: 'GET',
  263. url: '/organizations/org-slug/key-transactions-list/',
  264. body: teams.map(({id}) => ({
  265. team: id,
  266. count: 0,
  267. keyed: [],
  268. })),
  269. });
  270. const postTeamKeyTransactionsMock = MockApiClient.addMockResponse(
  271. {
  272. method: 'POST',
  273. url: '/organizations/org-slug/key-transactions/',
  274. body: [],
  275. },
  276. {
  277. predicate: (_, options) =>
  278. options.method === 'POST' &&
  279. options.query.project.length === 1 &&
  280. options.query.project[0] === project.id &&
  281. options.data.team.length === 2 &&
  282. options.data.team[0] === teams[0].id &&
  283. options.data.team[1] === teams[1].id &&
  284. options.data.transaction === 'transaction',
  285. }
  286. );
  287. const wrapper = mountWithTheme(
  288. <TeamKeyTransactionButton
  289. eventView={eventView}
  290. organization={organization}
  291. transactionName="transaction"
  292. />
  293. );
  294. await tick();
  295. wrapper.update();
  296. clickTeamKeyTransactionDropdown(wrapper);
  297. wrapper.find('DropdownMenuHeader CheckboxFancy').simulate('click');
  298. await tick();
  299. wrapper.update();
  300. // header should be checked now
  301. const headerCheckbox = wrapper.find('DropdownMenuHeader CheckboxFancy');
  302. expect(headerCheckbox.props().isChecked).toBeTruthy();
  303. expect(headerCheckbox.props().isIndeterminate).toBeFalsy();
  304. // all teams should be checked now
  305. const entries = wrapper.find('DropdownMenuItem');
  306. entries.forEach(entry => {
  307. expect(entry.find('CheckboxFancy').props().isChecked).toBeTruthy();
  308. });
  309. expect(postTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  310. });
  311. it('should be able to uncheck all with my teams', async function () {
  312. MockApiClient.addMockResponse({
  313. method: 'GET',
  314. url: '/organizations/org-slug/key-transactions-list/',
  315. body: teams.map(({id}) => ({
  316. team: id,
  317. count: 1,
  318. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  319. })),
  320. });
  321. const deleteTeamKeyTransactionsMock = MockApiClient.addMockResponse(
  322. {
  323. method: 'DELETE',
  324. url: '/organizations/org-slug/key-transactions/',
  325. body: [],
  326. },
  327. {
  328. predicate: (_, options) =>
  329. options.method === 'DELETE' &&
  330. options.query.project.length === 1 &&
  331. options.query.project[0] === project.id &&
  332. options.data.team.length === 2 &&
  333. options.data.team[0] === teams[0].id &&
  334. options.data.team[1] === teams[1].id &&
  335. options.data.transaction === 'transaction',
  336. }
  337. );
  338. const wrapper = mountWithTheme(
  339. <TeamKeyTransactionButton
  340. eventView={eventView}
  341. organization={organization}
  342. transactionName="transaction"
  343. />
  344. );
  345. await tick();
  346. wrapper.update();
  347. clickTeamKeyTransactionDropdown(wrapper);
  348. wrapper.find('DropdownMenuHeader CheckboxFancy').simulate('click');
  349. await tick();
  350. wrapper.update();
  351. // header should be unchecked now
  352. const headerCheckbox = wrapper.find('DropdownMenuHeader CheckboxFancy');
  353. expect(headerCheckbox.props().isChecked).toBeFalsy();
  354. expect(headerCheckbox.props().isIndeterminate).toBeFalsy();
  355. // all teams should be unchecked now
  356. const entries = wrapper.find('DropdownMenuItem');
  357. entries.forEach(entry => {
  358. expect(entry.find('CheckboxFancy').props().isChecked).toBeFalsy();
  359. });
  360. expect(deleteTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  361. });
  362. it('renders unkeyed as disabled if count exceeds max', async function () {
  363. MockApiClient.addMockResponse({
  364. method: 'GET',
  365. url: '/organizations/org-slug/key-transactions-list/',
  366. body: teams.map(({id}) => ({
  367. team: id,
  368. count: MAX_TEAM_KEY_TRANSACTIONS,
  369. keyed: Array.from({length: MAX_TEAM_KEY_TRANSACTIONS}, (_, i) => ({
  370. project_id: String(project.id),
  371. transaction: `transaction-${i}`,
  372. })),
  373. })),
  374. });
  375. const wrapper = mountWithTheme(
  376. <TeamKeyTransactionButton
  377. eventView={eventView}
  378. organization={organization}
  379. transactionName="transaction"
  380. />
  381. );
  382. await tick();
  383. wrapper.update();
  384. clickTeamKeyTransactionDropdown(wrapper);
  385. const entries = wrapper.find('DropdownMenuItem');
  386. expect(entries.length).toBe(2);
  387. entries.forEach((entry, i) => {
  388. expect(entry.props().disabled).toBeTruthy();
  389. expect(entry.text()).toEqual(`${teams[i].slug}Max ${MAX_TEAM_KEY_TRANSACTIONS}`);
  390. });
  391. });
  392. it('renders keyed as checked even if count is maxed', async function () {
  393. MockApiClient.addMockResponse({
  394. method: 'GET',
  395. url: '/organizations/org-slug/key-transactions-list/',
  396. body: teams.map(({id}) => ({
  397. team: id,
  398. count: MAX_TEAM_KEY_TRANSACTIONS,
  399. keyed: [
  400. {project_id: String(project.id), transaction: 'transaction'},
  401. ...Array.from({length: MAX_TEAM_KEY_TRANSACTIONS - 1}, (_, i) => ({
  402. project_id: String(project.id),
  403. transaction: `transaction-${i}`,
  404. })),
  405. ],
  406. })),
  407. });
  408. const wrapper = mountWithTheme(
  409. <TeamKeyTransactionButton
  410. eventView={eventView}
  411. organization={organization}
  412. transactionName="transaction"
  413. />
  414. );
  415. await tick();
  416. wrapper.update();
  417. clickTeamKeyTransactionDropdown(wrapper);
  418. const entries = wrapper.find('DropdownMenuItem');
  419. expect(entries.length).toBe(2);
  420. entries.forEach((entry, i) => {
  421. expect(entry.props().disabled).toBeFalsy();
  422. expect(entry.text()).toEqual(teams[i].slug);
  423. expect(entry.find('CheckboxFancy').props().isChecked).toBeTruthy();
  424. });
  425. });
  426. });