teamKeyTransactionButton.spec.jsx 14 KB

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