test_discover_key_transactions.py 35 KB

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