test_discover_key_transactions.py 35 KB

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