teamKeyTransactionButton.spec.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  2. import ProjectsStore from 'sentry/stores/projectsStore';
  3. import TeamStore from 'sentry/stores/teamStore';
  4. import EventView from 'sentry/utils/discover/eventView';
  5. import {MAX_TEAM_KEY_TRANSACTIONS} from 'sentry/utils/performance/constants';
  6. import TeamKeyTransactionButton from 'sentry/views/performance/transactionSummary/teamKeyTransactionButton';
  7. async function clickTeamKeyTransactionDropdown() {
  8. await waitFor(() =>
  9. expect(screen.getByRole('button', {expanded: false})).toBeEnabled()
  10. );
  11. await userEvent.click(screen.getByRole('button', {expanded: false}));
  12. }
  13. describe('TeamKeyTransactionButton', function () {
  14. const organization = TestStubs.Organization({features: ['performance-view']});
  15. const teams = [
  16. TestStubs.Team({id: '1', slug: 'team1', name: 'Team 1'}),
  17. TestStubs.Team({id: '2', slug: 'team2', name: 'Team 2'}),
  18. ];
  19. const project = TestStubs.Project({teams});
  20. const eventView = new EventView({
  21. id: '1',
  22. name: 'my query',
  23. fields: [{field: 'count()'}],
  24. sorts: [{field: 'count', kind: 'desc'}],
  25. query: '',
  26. project: [project.id],
  27. start: '2019-10-01T00:00:00',
  28. end: '2019-10-02T00:00:00',
  29. statsPeriod: '14d',
  30. environment: [],
  31. });
  32. beforeEach(function () {
  33. MockApiClient.clearMockResponses();
  34. act(() => ProjectsStore.loadInitialData([project]));
  35. act(() => void TeamStore.loadInitialData(teams, false, null));
  36. });
  37. it('fetches key transactions with project param', function () {
  38. const getTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  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. match: [MockApiClient.matchQuery({project: [project.id], team: ['myteams']})],
  47. });
  48. render(
  49. <TeamKeyTransactionButton
  50. eventView={eventView}
  51. organization={organization}
  52. transactionName="transaction"
  53. />
  54. );
  55. expect(getTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  56. });
  57. it('renders with all teams checked', async function () {
  58. MockApiClient.addMockResponse({
  59. method: 'GET',
  60. url: '/organizations/org-slug/key-transactions-list/',
  61. body: teams.map(({id}) => ({
  62. team: id,
  63. count: 1,
  64. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  65. })),
  66. });
  67. render(
  68. <TeamKeyTransactionButton
  69. eventView={eventView}
  70. organization={organization}
  71. transactionName="transaction"
  72. />
  73. );
  74. await clickTeamKeyTransactionDropdown();
  75. // all teams should be checked
  76. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  77. 'aria-selected',
  78. 'true'
  79. );
  80. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  81. 'aria-selected',
  82. 'true'
  83. );
  84. });
  85. it('renders with some teams checked', async function () {
  86. MockApiClient.addMockResponse({
  87. method: 'GET',
  88. url: '/organizations/org-slug/key-transactions-list/',
  89. body: teams.map(({id}) => ({
  90. team: id,
  91. count: id === teams[0].id ? 1 : 0,
  92. keyed:
  93. id === teams[0].id
  94. ? [{project_id: String(project.id), transaction: 'transaction'}]
  95. : [],
  96. })),
  97. });
  98. render(
  99. <TeamKeyTransactionButton
  100. eventView={eventView}
  101. organization={organization}
  102. transactionName="transaction"
  103. />
  104. );
  105. await clickTeamKeyTransactionDropdown();
  106. // only team 1 should be checked
  107. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  108. 'aria-selected',
  109. 'true'
  110. );
  111. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  112. 'aria-selected',
  113. 'false'
  114. );
  115. });
  116. it('renders with no teams checked', async function () {
  117. MockApiClient.addMockResponse({
  118. method: 'GET',
  119. url: '/organizations/org-slug/key-transactions-list/',
  120. body: teams.map(({id}) => ({
  121. team: id,
  122. count: 0,
  123. keyed: [],
  124. })),
  125. });
  126. render(
  127. <TeamKeyTransactionButton
  128. eventView={eventView}
  129. organization={organization}
  130. transactionName="transaction"
  131. />
  132. );
  133. await clickTeamKeyTransactionDropdown();
  134. // all teams should be unchecked
  135. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  136. 'aria-selected',
  137. 'false'
  138. );
  139. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  140. 'aria-selected',
  141. 'false'
  142. );
  143. });
  144. it('should be able to check one team', async function () {
  145. MockApiClient.addMockResponse({
  146. method: 'GET',
  147. url: '/organizations/org-slug/key-transactions-list/',
  148. body: teams.map(({id}) => ({
  149. team: id,
  150. count: 0,
  151. keyed: [],
  152. })),
  153. });
  154. const postTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  155. method: 'POST',
  156. url: '/organizations/org-slug/key-transactions/',
  157. body: [],
  158. match: [
  159. MockApiClient.matchQuery({project: [project.id]}),
  160. MockApiClient.matchData({team: [teams[0].id], transaction: 'transaction'}),
  161. ],
  162. });
  163. render(
  164. <TeamKeyTransactionButton
  165. eventView={eventView}
  166. organization={organization}
  167. transactionName="transaction"
  168. />
  169. );
  170. await clickTeamKeyTransactionDropdown();
  171. await userEvent.click(screen.getByRole('option', {name: `#${teams[0].slug}`}));
  172. expect(postTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  173. });
  174. it('should be able to uncheck 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: 1,
  181. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  182. })),
  183. });
  184. const deleteTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  185. method: 'DELETE',
  186. url: '/organizations/org-slug/key-transactions/',
  187. body: [],
  188. match: [
  189. MockApiClient.matchQuery({project: [project.id]}),
  190. MockApiClient.matchData({team: [teams[0].id], transaction: 'transaction'}),
  191. ],
  192. });
  193. render(
  194. <TeamKeyTransactionButton
  195. eventView={eventView}
  196. organization={organization}
  197. transactionName="transaction"
  198. />
  199. );
  200. await clickTeamKeyTransactionDropdown();
  201. await userEvent.click(screen.getByRole('option', {name: `#${teams[0].slug}`}));
  202. expect(deleteTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  203. });
  204. it('should be able to check all with my teams', async function () {
  205. MockApiClient.addMockResponse({
  206. method: 'GET',
  207. url: '/organizations/org-slug/key-transactions-list/',
  208. body: teams.map(({id}) => ({
  209. team: id,
  210. count: 0,
  211. keyed: [],
  212. })),
  213. });
  214. const postTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  215. method: 'POST',
  216. url: '/organizations/org-slug/key-transactions/',
  217. body: [],
  218. match: [
  219. MockApiClient.matchQuery({project: [project.id]}),
  220. MockApiClient.matchData({
  221. team: [teams[0].id, teams[1].id],
  222. transaction: 'transaction',
  223. }),
  224. ],
  225. });
  226. render(
  227. <TeamKeyTransactionButton
  228. eventView={eventView}
  229. organization={organization}
  230. transactionName="transaction"
  231. />
  232. );
  233. await clickTeamKeyTransactionDropdown();
  234. await userEvent.click(screen.getByRole('button', {name: 'Select All in My Teams'}));
  235. // all teams should be checked now
  236. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  237. 'aria-selected',
  238. 'true'
  239. );
  240. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  241. 'aria-selected',
  242. 'true'
  243. );
  244. expect(postTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  245. });
  246. it('should be able to uncheck all with my teams', async function () {
  247. MockApiClient.addMockResponse({
  248. method: 'GET',
  249. url: '/organizations/org-slug/key-transactions-list/',
  250. body: teams.map(({id}) => ({
  251. team: id,
  252. count: 1,
  253. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  254. })),
  255. });
  256. const deleteTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  257. method: 'DELETE',
  258. url: '/organizations/org-slug/key-transactions/',
  259. body: [],
  260. match: [
  261. MockApiClient.matchQuery({project: [project.id]}),
  262. MockApiClient.matchData({
  263. team: [teams[0].id, teams[1].id],
  264. transaction: 'transaction',
  265. }),
  266. ],
  267. });
  268. render(
  269. <TeamKeyTransactionButton
  270. eventView={eventView}
  271. organization={organization}
  272. transactionName="transaction"
  273. />
  274. );
  275. await clickTeamKeyTransactionDropdown();
  276. await userEvent.click(screen.getByRole('button', {name: 'Unselect All in My Teams'}));
  277. // all teams should be checked now
  278. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  279. 'aria-selected',
  280. 'false'
  281. );
  282. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  283. 'aria-selected',
  284. 'false'
  285. );
  286. expect(deleteTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  287. });
  288. it('renders unkeyed as disabled if count exceeds max', async function () {
  289. MockApiClient.addMockResponse({
  290. method: 'GET',
  291. url: '/organizations/org-slug/key-transactions-list/',
  292. body: teams.map(({id}) => ({
  293. team: id,
  294. count: MAX_TEAM_KEY_TRANSACTIONS,
  295. keyed: Array.from({length: MAX_TEAM_KEY_TRANSACTIONS}, (_, i) => ({
  296. project_id: String(project.id),
  297. transaction: `transaction-${i}`,
  298. })),
  299. })),
  300. });
  301. render(
  302. <TeamKeyTransactionButton
  303. eventView={eventView}
  304. organization={organization}
  305. transactionName="transaction"
  306. />
  307. );
  308. await clickTeamKeyTransactionDropdown();
  309. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  310. 'aria-disabled',
  311. 'true'
  312. );
  313. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  314. 'aria-disabled',
  315. 'true'
  316. );
  317. });
  318. it('renders keyed as checked even if count is maxed', async function () {
  319. MockApiClient.addMockResponse({
  320. method: 'GET',
  321. url: '/organizations/org-slug/key-transactions-list/',
  322. body: teams.map(({id}) => ({
  323. team: id,
  324. count: MAX_TEAM_KEY_TRANSACTIONS,
  325. keyed: [
  326. {project_id: String(project.id), transaction: 'transaction'},
  327. ...Array.from({length: MAX_TEAM_KEY_TRANSACTIONS - 1}, (_, i) => ({
  328. project_id: String(project.id),
  329. transaction: `transaction-${i}`,
  330. })),
  331. ],
  332. })),
  333. });
  334. render(
  335. <TeamKeyTransactionButton
  336. eventView={eventView}
  337. organization={organization}
  338. transactionName="transaction"
  339. />
  340. );
  341. await clickTeamKeyTransactionDropdown();
  342. expect(screen.getByRole('option', {name: `#${teams[0].slug}`})).toHaveAttribute(
  343. 'aria-selected',
  344. 'true'
  345. );
  346. expect(screen.getByRole('option', {name: `#${teams[1].slug}`})).toHaveAttribute(
  347. 'aria-selected',
  348. 'true'
  349. );
  350. });
  351. });