test_discover_key_transactions.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. from django.urls import reverse
  2. from sentry.discover.models import MAX_TEAM_KEY_TRANSACTIONS, TeamKeyTransaction
  3. from sentry.models import ProjectTeam
  4. from sentry.testutils import APITestCase, SnubaTestCase
  5. from sentry.testutils.helpers import parse_link_header
  6. from sentry.utils.samples import load_data
  7. class TeamKeyTransactionTestBase(APITestCase, SnubaTestCase):
  8. def setUp(self):
  9. super().setUp()
  10. self.login_as(user=self.user, superuser=False)
  11. self.org = self.create_organization(owner=self.user, name="foo")
  12. self.project = self.create_project(name="baz", organization=self.org)
  13. self.event_data = load_data("transaction")
  14. self.features = ["organizations:performance-view"]
  15. class TeamKeyTransactionTest(TeamKeyTransactionTestBase):
  16. def setUp(self):
  17. super().setUp()
  18. self.url = reverse("sentry-api-0-organization-key-transactions", args=[self.org.slug])
  19. def test_key_transaction_without_feature(self):
  20. project = self.create_project(name="qux", organization=self.org)
  21. functions = [self.client.get, self.client.post, self.client.delete]
  22. for function in functions:
  23. response = function(
  24. self.url,
  25. data={
  26. "project": [self.project.id, project.id],
  27. "transaction": self.event_data["transaction"],
  28. "team": "myteams",
  29. },
  30. format="json",
  31. )
  32. assert response.status_code == 404, response.content
  33. def test_get_key_transaction_multiple_projects(self):
  34. project = self.create_project(name="qux", organization=self.org)
  35. with self.feature(self.features):
  36. response = self.client.get(
  37. self.url,
  38. data={
  39. "project": [self.project.id, project.id],
  40. "transaction": self.event_data["transaction"],
  41. },
  42. format="json",
  43. )
  44. assert response.status_code == 400, response.content
  45. assert response.data == {"detail": "Only 1 project per Key Transaction"}
  46. def test_get_key_transaction_no_transaction_name(self):
  47. with self.feature(self.features):
  48. response = self.client.get(
  49. self.url,
  50. data={
  51. "project": [self.project.id],
  52. },
  53. format="json",
  54. )
  55. assert response.status_code == 400, response.content
  56. assert response.data == {"detail": "A transaction name is required"}
  57. def test_get_no_key_transaction(self):
  58. with self.feature(self.features):
  59. response = self.client.get(
  60. self.url,
  61. data={
  62. "project": [self.project.id],
  63. "transaction": self.event_data["transaction"],
  64. },
  65. format="json",
  66. )
  67. assert response.status_code == 200, response.content
  68. assert response.data == []
  69. def test_get_key_transaction_my_teams(self):
  70. team1 = self.create_team(organization=self.org, name="Team A")
  71. team2 = self.create_team(organization=self.org, name="Team B")
  72. team3 = self.create_team(organization=self.org, name="Team C")
  73. # should not be in response because we never joined this team
  74. self.create_team(organization=self.org, name="Team D")
  75. # only join teams 1,2,3
  76. for team in [team1, team2, team3]:
  77. self.create_team_membership(team, user=self.user)
  78. self.project.add_team(team)
  79. TeamKeyTransaction.objects.bulk_create(
  80. [
  81. TeamKeyTransaction(
  82. organization=self.org,
  83. project_team=project_team,
  84. transaction=self.event_data["transaction"],
  85. )
  86. for project_team in ProjectTeam.objects.filter(
  87. project=self.project, team__in=[team1, team2]
  88. )
  89. ]
  90. + [
  91. TeamKeyTransaction(
  92. organization=self.org,
  93. project_team=project_team,
  94. transaction="other-transaction",
  95. )
  96. for project_team in ProjectTeam.objects.filter(
  97. project=self.project, team__in=[team2, team3]
  98. )
  99. ]
  100. )
  101. with self.feature(self.features):
  102. response = self.client.get(
  103. self.url,
  104. data={
  105. "project": [self.project.id],
  106. "transaction": self.event_data["transaction"],
  107. "team": "myteams",
  108. },
  109. format="json",
  110. )
  111. assert response.status_code == 200, response.content
  112. assert response.data == [
  113. {
  114. "team": str(team1.id),
  115. },
  116. {
  117. "team": str(team2.id),
  118. },
  119. ]
  120. def test_post_key_transaction_more_than_1_project(self):
  121. team = self.create_team(organization=self.org, name="Team Foo")
  122. self.create_team_membership(team, user=self.user)
  123. self.project.add_team(team)
  124. project = self.create_project(name="bar", organization=self.org)
  125. project.add_team(team)
  126. with self.feature(self.features):
  127. response = self.client.post(
  128. self.url,
  129. data={
  130. "project": [self.project.id, project.id],
  131. "transaction": self.event_data["transaction"],
  132. "team": [team.id],
  133. },
  134. format="json",
  135. )
  136. assert response.status_code == 400, response.content
  137. assert response.data == {"detail": "Only 1 project per Key Transaction"}
  138. def test_post_key_transaction_no_team(self):
  139. team = self.create_team(organization=self.org, name="Team Foo")
  140. self.create_team_membership(team, user=self.user)
  141. self.project.add_team(team)
  142. with self.feature(self.features):
  143. response = self.client.post(
  144. self.url,
  145. data={
  146. "project": [self.project.id],
  147. "transaction": self.event_data["transaction"],
  148. },
  149. format="json",
  150. )
  151. assert response.status_code == 400, response.content
  152. assert response.data == {"team": ["This field is required."]}
  153. def test_post_key_transaction_no_transaction_name(self):
  154. team = self.create_team(organization=self.org, name="Team Foo")
  155. self.create_team_membership(team, user=self.user)
  156. self.project.add_team(team)
  157. with self.feature(self.features):
  158. response = self.client.post(
  159. self.url,
  160. data={
  161. "project": [self.project.id],
  162. "team": [team.id],
  163. },
  164. format="json",
  165. )
  166. assert response.status_code == 400, response.content
  167. assert response.data == {"transaction": ["This field is required."]}
  168. def test_post_key_transaction_no_access_team(self):
  169. org = self.create_organization(
  170. owner=self.user, # use other user as owner
  171. name="foo",
  172. flags=0, # disable default allow_joinleave
  173. )
  174. project = self.create_project(name="baz", organization=org)
  175. user = self.create_user()
  176. self.login_as(user=user, superuser=False)
  177. team = self.create_team(organization=org, name="Team Foo")
  178. self.create_team_membership(team, user=user)
  179. project.add_team(team)
  180. other_team = self.create_team(organization=org, name="Team Bar")
  181. project.add_team(other_team)
  182. with self.feature(self.features):
  183. response = self.client.post(
  184. reverse("sentry-api-0-organization-key-transactions", args=[org.slug]),
  185. data={
  186. "project": [project.id],
  187. "transaction": self.event_data["transaction"],
  188. "team": [other_team.id],
  189. },
  190. format="json",
  191. )
  192. assert response.status_code == 400, response.content
  193. assert response.data == {
  194. "team": [f"You do not have permission to access {other_team.name}"]
  195. }
  196. def test_post_key_transaction_no_access_project(self):
  197. team1 = self.create_team(organization=self.org, name="Team Foo")
  198. self.create_team_membership(team1, user=self.user)
  199. self.project.add_team(team1)
  200. team2 = self.create_team(organization=self.org, name="Team Bar")
  201. self.create_team_membership(team2, user=self.user)
  202. with self.feature(self.features):
  203. response = self.client.post(
  204. self.url,
  205. data={
  206. "project": [self.project.id],
  207. "transaction": self.event_data["transaction"],
  208. "team": [team2.id],
  209. },
  210. format="json",
  211. )
  212. assert response.status_code == 400, response.content
  213. assert response.data == {"detail": "Team does not have access to project"}
  214. def test_post_key_transactions_exceed_limit(self):
  215. team = self.create_team(organization=self.org, name="Team Foo")
  216. self.create_team_membership(team, user=self.user)
  217. self.project.add_team(team)
  218. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  219. TeamKeyTransaction.objects.bulk_create(
  220. [
  221. TeamKeyTransaction(
  222. organization=self.org,
  223. project_team=project_team,
  224. transaction=f"{self.event_data['transaction']}-{i}",
  225. )
  226. for i in range(MAX_TEAM_KEY_TRANSACTIONS)
  227. ]
  228. )
  229. with self.feature(self.features):
  230. response = self.client.post(
  231. self.url,
  232. data={
  233. "project": [self.project.id],
  234. "transaction": self.event_data["transaction"],
  235. "team": [team.id],
  236. },
  237. format="json",
  238. )
  239. assert response.status_code == 400, response.content
  240. assert response.data == {
  241. "non_field_errors": [
  242. f"At most {MAX_TEAM_KEY_TRANSACTIONS} Key Transactions can be added for a team"
  243. ]
  244. }
  245. def test_post_key_transaction_limit_is_per_team(self):
  246. team1 = self.create_team(organization=self.org, name="Team Foo")
  247. self.create_team_membership(team1, user=self.user)
  248. self.project.add_team(team1)
  249. team2 = self.create_team(organization=self.org, name="Team Bar")
  250. self.create_team_membership(team2, user=self.user)
  251. self.project.add_team(team2)
  252. project_teams = ProjectTeam.objects.filter(project=self.project, team__in=[team1, team2])
  253. TeamKeyTransaction.objects.bulk_create(
  254. [
  255. TeamKeyTransaction(
  256. organization=self.org,
  257. project_team=project_team,
  258. transaction=f"{self.event_data['transaction']}-{i}",
  259. )
  260. for project_team in project_teams
  261. for i in range(MAX_TEAM_KEY_TRANSACTIONS - 1)
  262. ]
  263. )
  264. with self.feature(self.features):
  265. response = self.client.post(
  266. self.url,
  267. data={
  268. "project": [self.project.id],
  269. "transaction": self.event_data["transaction"],
  270. "team": [team1.id, team2.id],
  271. },
  272. format="json",
  273. )
  274. assert response.status_code == 201, response.content
  275. key_transactions = TeamKeyTransaction.objects.filter(project_team__team__in=[team1, team2])
  276. assert len(key_transactions) == 2 * MAX_TEAM_KEY_TRANSACTIONS
  277. def test_post_key_transactions(self):
  278. team = self.create_team(organization=self.org, name="Team Foo")
  279. self.create_team_membership(team, user=self.user)
  280. self.project.add_team(team)
  281. with self.feature(self.features):
  282. response = self.client.post(
  283. self.url,
  284. data={
  285. "project": [self.project.id],
  286. "transaction": self.event_data["transaction"],
  287. "team": [team.id],
  288. },
  289. format="json",
  290. )
  291. assert response.status_code == 201, response.content
  292. key_transactions = TeamKeyTransaction.objects.filter(project_team__team=team)
  293. assert len(key_transactions) == 1
  294. def test_post_key_transactions_duplicate(self):
  295. team = self.create_team(organization=self.org, name="Team Foo")
  296. self.create_team_membership(team, user=self.user)
  297. self.project.add_team(team)
  298. project_team = ProjectTeam.objects.get(project=self.project, team=team)
  299. TeamKeyTransaction.objects.create(
  300. organization=self.org,
  301. project_team=project_team,
  302. transaction=self.event_data["transaction"],
  303. )
  304. with self.feature(self.features):
  305. response = self.client.post(
  306. self.url,
  307. data={
  308. "project": [self.project.id],
  309. "transaction": self.event_data["transaction"],
  310. "team": [team.id],
  311. },
  312. format="json",
  313. )
  314. assert response.status_code == 204, response.content
  315. key_transactions = TeamKeyTransaction.objects.filter(
  316. project_team=project_team, transaction=self.event_data["transaction"]
  317. )
  318. assert len(key_transactions) == 1
  319. def test_post_key_transaction_multiple_team(self):
  320. team1 = self.create_team(organization=self.org, name="Team Foo")
  321. self.create_team_membership(team1, user=self.user)
  322. self.project.add_team(team1)
  323. team2 = self.create_team(organization=self.org, name="Team Bar")
  324. self.create_team_membership(team2, user=self.user)
  325. self.project.add_team(team2)
  326. with self.feature(self.features):
  327. response = self.client.post(
  328. self.url,
  329. data={
  330. "project": [self.project.id],
  331. "transaction": self.event_data["transaction"],
  332. "team": [team1.id, team2.id],
  333. },
  334. format="json",
  335. )
  336. assert response.status_code == 201, response.content
  337. key_transactions = TeamKeyTransaction.objects.filter(
  338. project_team__in=ProjectTeam.objects.filter(
  339. project=self.project, team__in=[team1, team2]
  340. ),
  341. transaction=self.event_data["transaction"],
  342. )
  343. assert len(key_transactions) == 2
  344. def test_post_key_transaction_partially_existing_teams(self):
  345. team1 = self.create_team(organization=self.org, name="Team Foo")
  346. self.create_team_membership(team1, user=self.user)
  347. self.project.add_team(team1)
  348. team2 = self.create_team(organization=self.org, name="Team Bar")
  349. self.create_team_membership(team2, user=self.user)
  350. self.project.add_team(team2)
  351. TeamKeyTransaction.objects.create(
  352. organization=self.org,
  353. project_team=ProjectTeam.objects.get(project=self.project, team=team1),
  354. transaction=self.event_data["transaction"],
  355. )
  356. with self.feature(self.features):
  357. response = self.client.post(
  358. self.url,
  359. data={
  360. "project": [self.project.id],
  361. "transaction": self.event_data["transaction"],
  362. "team": [team1.id, team2.id],
  363. },
  364. format="json",
  365. )
  366. assert response.status_code == 201, response.content
  367. key_transactions = TeamKeyTransaction.objects.filter(
  368. project_team__in=ProjectTeam.objects.filter(
  369. project=self.project, team__in=[team1, team2]
  370. ),
  371. transaction=self.event_data["transaction"],
  372. )
  373. assert len(key_transactions) == 2
  374. def test_post_key_transaction_multiple_users(self):
  375. team = self.create_team(organization=self.org, name="Team Foo")
  376. self.create_team_membership(team, user=self.user)
  377. self.project.add_team(team)
  378. with self.feature(self.features):
  379. response = self.client.post(
  380. self.url,
  381. data={
  382. "project": [self.project.id],
  383. "transaction": self.event_data["transaction"],
  384. "team": [team.id],
  385. },
  386. format="json",
  387. )
  388. assert response.status_code == 201, response.content
  389. with self.feature(self.features):
  390. response = self.client.post(
  391. self.url,
  392. data={
  393. "project": [self.project.id],
  394. "transaction": self.event_data["transaction"],
  395. "team": [team.id],
  396. },
  397. format="json",
  398. )
  399. # already created by this user, so it's 204 this time
  400. assert response.status_code == 204, response.content
  401. user = self.create_user()
  402. self.create_member(user=user, organization=self.org, role="member")
  403. self.create_team_membership(team, user=user)
  404. self.login_as(user=user, superuser=False)
  405. with self.feature(self.features):
  406. response = self.client.post(
  407. self.url,
  408. data={
  409. "project": [self.project.id],
  410. "transaction": self.event_data["transaction"],
  411. "team": [team.id],
  412. },
  413. format="json",
  414. )
  415. # already created by another user, so it's 204 this time
  416. assert response.status_code == 204, response.content
  417. # should only create 1 team key transaction
  418. key_transactions = TeamKeyTransaction.objects.filter(project_team__team=team)
  419. assert len(key_transactions) == 1
  420. def test_post_key_transaction_overly_long_transaction(self):
  421. team = self.create_team(organization=self.org, name="Team Foo")
  422. self.create_team_membership(team, user=self.user)
  423. self.project.add_team(team)
  424. with self.feature(self.features):
  425. response = self.client.post(
  426. self.url,
  427. data={
  428. "project": [self.project.id],
  429. "transaction": "a" * 500,
  430. "team": [team.id],
  431. },
  432. format="json",
  433. )
  434. assert response.status_code == 400, response.content
  435. assert response.data == {
  436. "transaction": ["Ensure this field has no more than 200 characters."]
  437. }
  438. def test_delete_key_transaction_no_transaction_name(self):
  439. team = self.create_team(organization=self.org, name="Team Foo")
  440. self.create_team_membership(team, user=self.user)
  441. self.project.add_team(team)
  442. with self.feature(self.features):
  443. response = self.client.delete(
  444. self.url,
  445. data={
  446. "project": [self.project.id],
  447. "team": [team.id],
  448. },
  449. format="json",
  450. )
  451. assert response.status_code == 400, response.content
  452. assert response.data == {"transaction": ["This field is required."]}
  453. def test_delete_key_transaction_no_team(self):
  454. team = self.create_team(organization=self.org, name="Team Foo")
  455. self.create_team_membership(team, user=self.user)
  456. self.project.add_team(team)
  457. with self.feature(self.features):
  458. response = self.client.delete(
  459. self.url,
  460. data={
  461. "project": [self.project.id],
  462. "transaction": self.event_data["transaction"],
  463. },
  464. format="json",
  465. )
  466. assert response.status_code == 400, response.content
  467. assert response.data == {"team": ["This field is required."]}
  468. def test_delete_key_transactions_no_exist(self):
  469. team = self.create_team(organization=self.org, name="Team Foo")
  470. self.create_team_membership(team, user=self.user)
  471. self.project.add_team(team)
  472. with self.feature(self.features):
  473. response = self.client.delete(
  474. self.url,
  475. data={
  476. "project": [self.project.id],
  477. "transaction": self.event_data["transaction"],
  478. "team": [team.id],
  479. },
  480. format="json",
  481. )
  482. assert response.status_code == 204, response.content
  483. key_transactions = TeamKeyTransaction.objects.filter(project_team__team=team)
  484. assert len(key_transactions) == 0
  485. def test_delete_key_transaction_no_access_team(self):
  486. org = self.create_organization(
  487. owner=self.user, # use other user as owner
  488. name="foo",
  489. flags=0, # disable default allow_joinleave
  490. )
  491. project = self.create_project(name="baz", organization=org)
  492. user = self.create_user()
  493. self.login_as(user=user, superuser=False)
  494. team = self.create_team(organization=org, name="Team Foo")
  495. self.create_team_membership(team, user=user)
  496. project.add_team(team)
  497. other_team = self.create_team(organization=org, name="Team Bar")
  498. project.add_team(other_team)
  499. TeamKeyTransaction.objects.create(
  500. organization=org,
  501. project_team=ProjectTeam.objects.get(project=project, team=team),
  502. transaction=self.event_data["transaction"],
  503. )
  504. with self.feature(self.features):
  505. response = self.client.delete(
  506. reverse("sentry-api-0-organization-key-transactions", args=[org.slug]),
  507. data={
  508. "project": [project.id],
  509. "transaction": self.event_data["transaction"],
  510. "team": [other_team.id],
  511. },
  512. format="json",
  513. )
  514. assert response.status_code == 400, response.content
  515. assert response.data == {
  516. "team": [f"You do not have permission to access {other_team.name}"]
  517. }
  518. def test_delete_key_transactions(self):
  519. team = self.create_team(organization=self.org, name="Team Foo")
  520. self.create_team_membership(team, user=self.user)
  521. self.project.add_team(team)
  522. TeamKeyTransaction.objects.create(
  523. organization=self.org,
  524. project_team=ProjectTeam.objects.get(project=self.project, team=team),
  525. transaction=self.event_data["transaction"],
  526. )
  527. with self.feature(self.features):
  528. response = self.client.delete(
  529. self.url,
  530. data={
  531. "project": [self.project.id],
  532. "transaction": self.event_data["transaction"],
  533. "team": [team.id],
  534. },
  535. format="json",
  536. )
  537. assert response.status_code == 204, response.content
  538. key_transactions = TeamKeyTransaction.objects.filter(project_team__team=team)
  539. assert len(key_transactions) == 0
  540. def test_delete_key_transaction_partially_existing_teams(self):
  541. team1 = self.create_team(organization=self.org, name="Team Foo")
  542. self.create_team_membership(team1, user=self.user)
  543. self.project.add_team(team1)
  544. team2 = self.create_team(organization=self.org, name="Team Bar")
  545. self.create_team_membership(team2, user=self.user)
  546. self.project.add_team(team2)
  547. TeamKeyTransaction.objects.create(
  548. organization=self.org,
  549. project_team=ProjectTeam.objects.get(project=self.project, team=team1),
  550. transaction=self.event_data["transaction"],
  551. )
  552. with self.feature(self.features):
  553. response = self.client.delete(
  554. self.url,
  555. data={
  556. "project": [self.project.id],
  557. "transaction": self.event_data["transaction"],
  558. "team": [team1.id, team2.id],
  559. },
  560. format="json",
  561. )
  562. assert response.status_code == 204, response.content
  563. class TeamKeyTransactionListTest(TeamKeyTransactionTestBase):
  564. def setUp(self):
  565. super().setUp()
  566. self.url = reverse("sentry-api-0-organization-key-transactions-list", args=[self.org.slug])
  567. self.team1 = self.create_team(organization=self.org, name="Team A")
  568. self.team2 = self.create_team(organization=self.org, name="Team B")
  569. self.team3 = self.create_team(organization=self.org, name="Team C")
  570. self.team4 = self.create_team(organization=self.org, name="Team D")
  571. self.team5 = self.create_team(organization=self.org, name="Team E")
  572. for team in [self.team1, self.team2, self.team3, self.team4, self.team5]:
  573. self.project.add_team(team)
  574. # only join teams 1,2,3
  575. for team in [self.team1, self.team2, self.team3]:
  576. self.create_team_membership(team, user=self.user)
  577. TeamKeyTransaction.objects.bulk_create(
  578. [
  579. TeamKeyTransaction(
  580. organization=self.org,
  581. project_team=project_team,
  582. transaction=self.event_data["transaction"],
  583. )
  584. for project_team in ProjectTeam.objects.filter(
  585. project=self.project, team__in=[self.team2, self.team3]
  586. )
  587. ]
  588. + [
  589. TeamKeyTransaction(
  590. organization=self.org,
  591. project_team=project_team,
  592. transaction="other-transaction",
  593. )
  594. for project_team in ProjectTeam.objects.filter(
  595. project=self.project, team__in=[self.team3, self.team4]
  596. )
  597. ]
  598. )
  599. def test_get_key_transaction_list_no_permissions(self):
  600. org = self.create_organization(
  601. owner=self.user, # use other user as owner
  602. name="foo",
  603. flags=0, # disable default allow_joinleave
  604. )
  605. project = self.create_project(name="baz", organization=org)
  606. user = self.create_user()
  607. self.login_as(user=user, superuser=False)
  608. team = self.create_team(organization=org, name="Team Foo")
  609. self.create_team_membership(team, user=user)
  610. project.add_team(team)
  611. other_team = self.create_team(organization=org, name="Team Bar")
  612. project.add_team(other_team)
  613. with self.feature(self.features):
  614. response = self.client.get(
  615. reverse("sentry-api-0-organization-key-transactions-list", args=[org.slug]),
  616. data={
  617. "project": [self.project.id],
  618. "team": ["myteams", other_team.id],
  619. },
  620. format="json",
  621. )
  622. assert response.status_code == 403, response.content
  623. assert response.data == {
  624. "detail": f"Error: You do not have permission to access {other_team.name}"
  625. }
  626. def test_get_key_transaction_list_my_teams(self):
  627. with self.feature(self.features):
  628. response = self.client.get(
  629. self.url,
  630. data={
  631. "project": [self.project.id],
  632. "team": ["myteams"],
  633. },
  634. format="json",
  635. )
  636. assert response.status_code == 200, response.content
  637. assert response.data == [
  638. {
  639. "team": str(self.team1.id),
  640. "count": 0,
  641. "keyed": [],
  642. },
  643. {
  644. "team": str(self.team2.id),
  645. "count": 1,
  646. "keyed": [
  647. {
  648. "project_id": str(self.project.id),
  649. "transaction": self.event_data["transaction"],
  650. },
  651. ],
  652. },
  653. {
  654. "team": str(self.team3.id),
  655. "count": 2,
  656. "keyed": [
  657. {
  658. "project_id": str(self.project.id),
  659. "transaction": self.event_data["transaction"],
  660. },
  661. {"project_id": str(self.project.id), "transaction": "other-transaction"},
  662. ],
  663. },
  664. ]
  665. def test_get_key_transaction_list_other_teams(self):
  666. with self.feature(self.features):
  667. response = self.client.get(
  668. self.url,
  669. data={
  670. "project": [self.project.id],
  671. "team": [self.team4.id, self.team5.id],
  672. },
  673. format="json",
  674. )
  675. assert response.status_code == 200, response.content
  676. assert response.data == [
  677. {
  678. "team": str(self.team4.id),
  679. "count": 1,
  680. "keyed": [
  681. {"project_id": str(self.project.id), "transaction": "other-transaction"},
  682. ],
  683. },
  684. {
  685. "team": str(self.team5.id),
  686. "count": 0,
  687. "keyed": [],
  688. },
  689. ]
  690. def test_get_key_transaction_list_mixed_my_and_other_teams(self):
  691. with self.feature(self.features):
  692. response = self.client.get(
  693. self.url,
  694. data={
  695. "project": [self.project.id],
  696. "team": ["myteams", self.team4.id, self.team5.id],
  697. },
  698. format="json",
  699. )
  700. assert response.status_code == 200, response.content
  701. assert response.data == [
  702. {
  703. "team": str(self.team1.id),
  704. "count": 0,
  705. "keyed": [],
  706. },
  707. {
  708. "team": str(self.team2.id),
  709. "count": 1,
  710. "keyed": [
  711. {
  712. "project_id": str(self.project.id),
  713. "transaction": self.event_data["transaction"],
  714. },
  715. ],
  716. },
  717. {
  718. "team": str(self.team3.id),
  719. "count": 2,
  720. "keyed": [
  721. {
  722. "project_id": str(self.project.id),
  723. "transaction": self.event_data["transaction"],
  724. },
  725. {"project_id": str(self.project.id), "transaction": "other-transaction"},
  726. ],
  727. },
  728. {
  729. "team": str(self.team4.id),
  730. "count": 1,
  731. "keyed": [
  732. {"project_id": str(self.project.id), "transaction": "other-transaction"},
  733. ],
  734. },
  735. {
  736. "team": str(self.team5.id),
  737. "count": 0,
  738. "keyed": [],
  739. },
  740. ]
  741. def test_get_key_transaction_list_pagination(self):
  742. user = self.create_user()
  743. self.login_as(user=user)
  744. org = self.create_organization(owner=user, name="foo")
  745. project = self.create_project(name="baz", organization=org)
  746. teams = []
  747. for i in range(123):
  748. team = self.create_team(organization=org, name=f"Team {i:02d}")
  749. self.create_team_membership(team, user=user)
  750. project.add_team(team)
  751. teams.append(team)
  752. # get the first page
  753. with self.feature(self.features):
  754. response = self.client.get(
  755. reverse("sentry-api-0-organization-key-transactions-list", args=[org.slug]),
  756. data={
  757. "project": [project.id],
  758. "team": ["myteams"],
  759. },
  760. format="json",
  761. )
  762. assert response.status_code == 200, response.content
  763. assert len(response.data) == 100
  764. links = {
  765. link["rel"]: {"url": url, **link}
  766. for url, link in parse_link_header(response["Link"]).items()
  767. }
  768. assert links["previous"]["results"] == "false"
  769. assert links["next"]["results"] == "true"
  770. # get the second page
  771. with self.feature(self.features):
  772. response = self.client.get(
  773. reverse("sentry-api-0-organization-key-transactions-list", args=[org.slug]),
  774. data={
  775. "project": [project.id],
  776. "team": ["myteams"],
  777. "cursor": links["next"]["cursor"],
  778. },
  779. format="json",
  780. )
  781. assert response.status_code == 200, response.content
  782. assert len(response.data) == 23
  783. links = {
  784. link["rel"]: {"url": url, **link}
  785. for url, link in parse_link_header(response["Link"]).items()
  786. }
  787. assert links["previous"]["results"] == "true"
  788. assert links["next"]["results"] == "false"
  789. def test_get_key_transaction_list_partial_project(self):
  790. another_project = self.create_project(organization=self.org)
  791. another_project.add_team(self.team2)
  792. TeamKeyTransaction.objects.create(
  793. organization=self.org,
  794. project_team=ProjectTeam.objects.get(project=another_project, team=self.team2),
  795. transaction="another-transaction",
  796. )
  797. with self.feature(self.features):
  798. response = self.client.get(
  799. self.url,
  800. data={
  801. "project": [another_project.id],
  802. "team": [self.team2.id],
  803. },
  804. format="json",
  805. )
  806. assert response.status_code == 200, response.content
  807. assert response.data == [
  808. {
  809. "team": str(self.team2.id),
  810. # the key transaction in self.project is counted but not in
  811. # the list because self.project is not in the project param
  812. "count": 2,
  813. "keyed": [
  814. {
  815. "project_id": str(another_project.id),
  816. "transaction": "another-transaction",
  817. },
  818. ],
  819. },
  820. ]