test_discover_key_transactions.py 34 KB

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