teamKeyTransactionButton.spec.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  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(() => expect(screen.getByRole('button')).toBeEnabled());
  9. await userEvent.click(screen.getByRole('button'));
  10. }
  11. describe('TeamKeyTransactionButton', function () {
  12. const organization = TestStubs.Organization({features: ['performance-view']});
  13. const teams = [
  14. TestStubs.Team({id: '1', slug: 'team1', name: 'Team 1'}),
  15. TestStubs.Team({id: '2', slug: 'team2', name: 'Team 2'}),
  16. ];
  17. const project = TestStubs.Project({teams});
  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. act(() => ProjectsStore.loadInitialData([project]));
  33. act(() => void TeamStore.loadInitialData(teams, false, null));
  34. });
  35. it('fetches key transactions with project param', function () {
  36. const getTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  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. match: [MockApiClient.matchQuery({project: [project.id], team: ['myteams']})],
  45. });
  46. render(
  47. <TeamKeyTransactionButton
  48. eventView={eventView}
  49. organization={organization}
  50. transactionName="transaction"
  51. />
  52. );
  53. expect(getTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  54. });
  55. it('renders with all teams checked', async function () {
  56. MockApiClient.addMockResponse({
  57. method: 'GET',
  58. url: '/organizations/org-slug/key-transactions-list/',
  59. body: teams.map(({id}) => ({
  60. team: id,
  61. count: 1,
  62. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  63. })),
  64. });
  65. render(
  66. <TeamKeyTransactionButton
  67. eventView={eventView}
  68. organization={organization}
  69. transactionName="transaction"
  70. />
  71. );
  72. await clickTeamKeyTransactionDropdown();
  73. // header should show the checked state
  74. expect(screen.getByRole('checkbox', {name: 'My Teams with Access'})).toBeChecked();
  75. // all teams should be checked
  76. expect(screen.getByRole('checkbox', {name: teams[0].slug})).toBeChecked();
  77. expect(screen.getByRole('checkbox', {name: teams[1].slug})).toBeChecked();
  78. });
  79. it('renders with some teams checked', async function () {
  80. MockApiClient.addMockResponse({
  81. method: 'GET',
  82. url: '/organizations/org-slug/key-transactions-list/',
  83. body: teams.map(({id}) => ({
  84. team: id,
  85. count: id === teams[0].id ? 1 : 0,
  86. keyed:
  87. id === teams[0].id
  88. ? [{project_id: String(project.id), transaction: 'transaction'}]
  89. : [],
  90. })),
  91. });
  92. render(
  93. <TeamKeyTransactionButton
  94. eventView={eventView}
  95. organization={organization}
  96. transactionName="transaction"
  97. />
  98. );
  99. await clickTeamKeyTransactionDropdown();
  100. // header should show the indeterminate state
  101. expect(
  102. screen.getByRole('checkbox', {name: 'My Teams with Access'})
  103. ).toBePartiallyChecked();
  104. // only team 1 should be checked
  105. expect(screen.getByRole('checkbox', {name: teams[0].slug})).toBeChecked();
  106. expect(screen.getByRole('checkbox', {name: teams[1].slug})).not.toBeChecked();
  107. });
  108. it('renders with no teams checked', async function () {
  109. MockApiClient.addMockResponse({
  110. method: 'GET',
  111. url: '/organizations/org-slug/key-transactions-list/',
  112. body: teams.map(({id}) => ({
  113. team: id,
  114. count: 0,
  115. keyed: [],
  116. })),
  117. });
  118. render(
  119. <TeamKeyTransactionButton
  120. eventView={eventView}
  121. organization={organization}
  122. transactionName="transaction"
  123. />
  124. );
  125. await clickTeamKeyTransactionDropdown();
  126. // header should show the unchecked state
  127. expect(
  128. screen.getByRole('checkbox', {name: 'My Teams with Access'})
  129. ).not.toBeChecked();
  130. // all teams should be unchecked
  131. expect(screen.getByRole('checkbox', {name: teams[0].slug})).not.toBeChecked();
  132. expect(screen.getByRole('checkbox', {name: teams[1].slug})).not.toBeChecked();
  133. });
  134. it('should be able to check one team', async function () {
  135. MockApiClient.addMockResponse({
  136. method: 'GET',
  137. url: '/organizations/org-slug/key-transactions-list/',
  138. body: teams.map(({id}) => ({
  139. team: id,
  140. count: 0,
  141. keyed: [],
  142. })),
  143. });
  144. const postTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  145. method: 'POST',
  146. url: '/organizations/org-slug/key-transactions/',
  147. body: [],
  148. match: [
  149. MockApiClient.matchQuery({project: [project.id]}),
  150. MockApiClient.matchData({team: [teams[0].id], transaction: 'transaction'}),
  151. ],
  152. });
  153. render(
  154. <TeamKeyTransactionButton
  155. eventView={eventView}
  156. organization={organization}
  157. transactionName="transaction"
  158. />
  159. );
  160. await clickTeamKeyTransactionDropdown();
  161. await userEvent.click(screen.getByRole('checkbox', {name: teams[0].slug}));
  162. expect(postTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  163. });
  164. it('should be able to uncheck one team', async function () {
  165. MockApiClient.addMockResponse({
  166. method: 'GET',
  167. url: '/organizations/org-slug/key-transactions-list/',
  168. body: teams.map(({id}) => ({
  169. team: id,
  170. count: 1,
  171. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  172. })),
  173. });
  174. const deleteTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  175. method: 'DELETE',
  176. url: '/organizations/org-slug/key-transactions/',
  177. body: [],
  178. match: [
  179. MockApiClient.matchQuery({project: [project.id]}),
  180. MockApiClient.matchData({team: [teams[0].id], transaction: 'transaction'}),
  181. ],
  182. });
  183. render(
  184. <TeamKeyTransactionButton
  185. eventView={eventView}
  186. organization={organization}
  187. transactionName="transaction"
  188. />
  189. );
  190. await clickTeamKeyTransactionDropdown();
  191. await userEvent.click(screen.getByRole('checkbox', {name: teams[0].slug}));
  192. expect(deleteTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  193. });
  194. it('should be able to check all with my teams', async function () {
  195. MockApiClient.addMockResponse({
  196. method: 'GET',
  197. url: '/organizations/org-slug/key-transactions-list/',
  198. body: teams.map(({id}) => ({
  199. team: id,
  200. count: 0,
  201. keyed: [],
  202. })),
  203. });
  204. const postTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  205. method: 'POST',
  206. url: '/organizations/org-slug/key-transactions/',
  207. body: [],
  208. match: [
  209. MockApiClient.matchQuery({project: [project.id]}),
  210. MockApiClient.matchData({
  211. team: [teams[0].id, teams[1].id],
  212. transaction: 'transaction',
  213. }),
  214. ],
  215. });
  216. render(
  217. <TeamKeyTransactionButton
  218. eventView={eventView}
  219. organization={organization}
  220. transactionName="transaction"
  221. />
  222. );
  223. await clickTeamKeyTransactionDropdown();
  224. await userEvent.click(screen.getByRole('checkbox', {name: 'My Teams with Access'}));
  225. // all teams should be checked now
  226. await waitFor(() => {
  227. expect(screen.getByRole('checkbox', {name: teams[0].slug})).toBeChecked();
  228. expect(screen.getByRole('checkbox', {name: teams[1].slug})).toBeChecked();
  229. });
  230. expect(postTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  231. });
  232. it('should be able to uncheck all with my teams', async function () {
  233. MockApiClient.addMockResponse({
  234. method: 'GET',
  235. url: '/organizations/org-slug/key-transactions-list/',
  236. body: teams.map(({id}) => ({
  237. team: id,
  238. count: 1,
  239. keyed: [{project_id: String(project.id), transaction: 'transaction'}],
  240. })),
  241. });
  242. const deleteTeamKeyTransactionsMock = MockApiClient.addMockResponse({
  243. method: 'DELETE',
  244. url: '/organizations/org-slug/key-transactions/',
  245. body: [],
  246. match: [
  247. MockApiClient.matchQuery({project: [project.id]}),
  248. MockApiClient.matchData({
  249. team: [teams[0].id, teams[1].id],
  250. transaction: 'transaction',
  251. }),
  252. ],
  253. });
  254. render(
  255. <TeamKeyTransactionButton
  256. eventView={eventView}
  257. organization={organization}
  258. transactionName="transaction"
  259. />
  260. );
  261. await clickTeamKeyTransactionDropdown();
  262. await userEvent.click(screen.getByRole('checkbox', {name: 'My Teams with Access'}));
  263. // all teams should be unchecked now
  264. await waitFor(() => {
  265. expect(screen.getByRole('checkbox', {name: teams[0].slug})).not.toBeChecked();
  266. expect(screen.getByRole('checkbox', {name: teams[1].slug})).not.toBeChecked();
  267. });
  268. expect(deleteTeamKeyTransactionsMock).toHaveBeenCalledTimes(1);
  269. });
  270. it('renders unkeyed as disabled if count exceeds max', async function () {
  271. MockApiClient.addMockResponse({
  272. method: 'GET',
  273. url: '/organizations/org-slug/key-transactions-list/',
  274. body: teams.map(({id}) => ({
  275. team: id,
  276. count: MAX_TEAM_KEY_TRANSACTIONS,
  277. keyed: Array.from({length: MAX_TEAM_KEY_TRANSACTIONS}, (_, i) => ({
  278. project_id: String(project.id),
  279. transaction: `transaction-${i}`,
  280. })),
  281. })),
  282. });
  283. render(
  284. <TeamKeyTransactionButton
  285. eventView={eventView}
  286. organization={organization}
  287. transactionName="transaction"
  288. />
  289. );
  290. await clickTeamKeyTransactionDropdown();
  291. expect(
  292. screen.getByRole('button', {
  293. name: `${teams[0].slug} Max ${MAX_TEAM_KEY_TRANSACTIONS}`,
  294. })
  295. ).toBeInTheDocument();
  296. expect(
  297. screen.getByRole('button', {
  298. name: `${teams[1].slug} Max ${MAX_TEAM_KEY_TRANSACTIONS}`,
  299. })
  300. ).toBeInTheDocument();
  301. });
  302. it('renders keyed as checked even if count is maxed', async function () {
  303. MockApiClient.addMockResponse({
  304. method: 'GET',
  305. url: '/organizations/org-slug/key-transactions-list/',
  306. body: teams.map(({id}) => ({
  307. team: id,
  308. count: MAX_TEAM_KEY_TRANSACTIONS,
  309. keyed: [
  310. {project_id: String(project.id), transaction: 'transaction'},
  311. ...Array.from({length: MAX_TEAM_KEY_TRANSACTIONS - 1}, (_, i) => ({
  312. project_id: String(project.id),
  313. transaction: `transaction-${i}`,
  314. })),
  315. ],
  316. })),
  317. });
  318. render(
  319. <TeamKeyTransactionButton
  320. eventView={eventView}
  321. organization={organization}
  322. transactionName="transaction"
  323. />
  324. );
  325. await clickTeamKeyTransactionDropdown();
  326. expect(screen.getByRole('checkbox', {name: teams[0].slug})).toBeChecked();
  327. expect(screen.getByRole('checkbox', {name: teams[1].slug})).toBeChecked();
  328. });
  329. });