test_discover_key_transactions.py 34 KB

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