test_organization_spans_aggregation.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. import hashlib
  2. from datetime import timedelta
  3. from unittest import mock
  4. from uuid import uuid4
  5. from django.urls import reverse
  6. from snuba_sdk import Column, Condition, Function, Op
  7. from sentry.api.endpoints.organization_spans_aggregation import NULL_GROUP
  8. from sentry.testutils.cases import APITestCase, SnubaTestCase
  9. from sentry.testutils.helpers.datetime import before_now
  10. from sentry.testutils.helpers.options import override_options
  11. from sentry.testutils.pytest.fixtures import django_db_all
  12. from sentry.utils.samples import load_data
  13. MOCK_SNUBA_RESPONSE = {
  14. "data": [
  15. {
  16. "trace_id": "a" * 32,
  17. "transaction_id": "80fe542aea4945ffbe612646987ee449",
  18. "count": 71,
  19. "spans": [
  20. [
  21. "root_1",
  22. 1,
  23. "parent_1",
  24. "e238e6c2e2466b07",
  25. "api/0/foo",
  26. "other",
  27. "2023-09-13 17:12:19",
  28. 100,
  29. 1000,
  30. 1000,
  31. ],
  32. [
  33. "B1",
  34. 0,
  35. "root_1",
  36. "B",
  37. "connect",
  38. "db",
  39. "2023-09-13 17:12:19",
  40. 150,
  41. 50,
  42. 50.0,
  43. ],
  44. [
  45. "C1",
  46. 0,
  47. "root_1",
  48. "C",
  49. "resolve_conditions",
  50. "discover.endpoint",
  51. "2023-09-13 17:12:19",
  52. 155,
  53. 0,
  54. 10.0,
  55. ],
  56. [
  57. "D1",
  58. 0,
  59. "C1",
  60. "D",
  61. "resolve_orderby",
  62. "discover.snql",
  63. "2023-09-13 17:12:19",
  64. 157,
  65. 0,
  66. 20.0,
  67. ],
  68. [
  69. "E1",
  70. 0,
  71. "C1",
  72. NULL_GROUP,
  73. "resolve_columns",
  74. "discover.snql",
  75. "2023-09-13 17:12:19",
  76. 157,
  77. 0,
  78. 20.0,
  79. ],
  80. ],
  81. },
  82. {
  83. "trace_id": "b" * 32,
  84. "transaction_id": "86b21833d1854d9b811000b91e7fccfa",
  85. "count": 71,
  86. "spans": [
  87. [
  88. "root_2",
  89. 1,
  90. "parent_2",
  91. "e238e6c2e2466b07",
  92. "bind_organization_context",
  93. "other",
  94. "2023-09-13 17:12:39",
  95. 100,
  96. 700,
  97. 0.0,
  98. ],
  99. [
  100. "B2",
  101. 0,
  102. "root_2",
  103. "B",
  104. "connect",
  105. "db",
  106. "2023-09-13 17:12:39",
  107. 110,
  108. 10,
  109. 30.0,
  110. ],
  111. [
  112. "C2",
  113. 0,
  114. "root_2",
  115. "C",
  116. "resolve_conditions",
  117. "discover.endpoint",
  118. "2023-09-13 17:12:39",
  119. 115,
  120. 0,
  121. 40.0,
  122. ],
  123. [
  124. "D2",
  125. 0,
  126. "C2",
  127. "D",
  128. "resolve_orderby",
  129. "discover.snql",
  130. "2023-09-13 17:12:39",
  131. 150,
  132. 0,
  133. 10.0,
  134. ],
  135. [
  136. "D2-duplicate",
  137. 0,
  138. "C2",
  139. "D",
  140. "resolve_orderby",
  141. "discover.snql",
  142. "2023-09-13 17:12:40",
  143. 155,
  144. 0,
  145. 20.0,
  146. ],
  147. [
  148. "E2",
  149. 0,
  150. "C2",
  151. NULL_GROUP,
  152. "resolve_columns",
  153. "discover.snql",
  154. "2023-09-13 17:12:39",
  155. 157,
  156. 0,
  157. 20.0,
  158. ],
  159. ],
  160. },
  161. ]
  162. }
  163. class OrganizationIndexedSpansAggregationTest(APITestCase, SnubaTestCase):
  164. url_name = "sentry-api-0-organization-spans-aggregation"
  165. FEATURES = [
  166. "organizations:insights-initial-modules",
  167. "organizations:performance-view",
  168. ]
  169. def setUp(self):
  170. super().setUp()
  171. self.login_as(user=self.user)
  172. self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
  173. self.url = reverse(
  174. self.url_name,
  175. kwargs={"organization_id_or_slug": self.project.organization.slug},
  176. )
  177. @override_options({"indexed-spans.agg-span-waterfall.enable": True})
  178. @mock.patch("sentry.api.endpoints.organization_spans_aggregation.raw_snql_query")
  179. def test_without_flag(self, _mock_query):
  180. response = self.client.get(
  181. self.url,
  182. data={"transaction": "api/0/foo", "statsPeriod": "1d"},
  183. format="json",
  184. )
  185. assert response.status_code == 404
  186. @override_options({"indexed-spans.agg-span-waterfall.enable": True})
  187. @mock.patch("sentry.api.endpoints.organization_spans_aggregation.raw_snql_query")
  188. def test_simple(self, mock_query):
  189. mock_query.side_effect = [MOCK_SNUBA_RESPONSE]
  190. with self.feature(self.FEATURES):
  191. response = self.client.get(
  192. self.url,
  193. data={"transaction": "api/0/foo", "statsPeriod": "1d"},
  194. format="json",
  195. )
  196. assert response.data
  197. data = response.data
  198. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  199. assert root_fingerprint in data
  200. assert data[root_fingerprint]["count()"] == 2
  201. assert data[root_fingerprint]["description"] == "api/0/foo"
  202. assert round(data[root_fingerprint]["avg(duration)"]) == 850
  203. assert data[root_fingerprint]["samples"] == {
  204. ("80fe542aea4945ffbe612646987ee449", "root_1"),
  205. ("86b21833d1854d9b811000b91e7fccfa", "root_2"),
  206. }
  207. assert data[root_fingerprint]["sample_spans"] == [
  208. {
  209. "transaction": "80fe542aea4945ffbe612646987ee449",
  210. "timestamp": 1694625139.1,
  211. "span": "root_1",
  212. "trace": "a" * 32,
  213. },
  214. {
  215. "transaction": "86b21833d1854d9b811000b91e7fccfa",
  216. "timestamp": 1694625159.1,
  217. "span": "root_2",
  218. "trace": "b" * 32,
  219. },
  220. ]
  221. fingerprint = hashlib.md5(b"e238e6c2e2466b07-B").hexdigest()[:16]
  222. assert data[fingerprint]["description"] == "connect"
  223. assert round(data[fingerprint]["avg(duration)"]) == 30
  224. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D").hexdigest()[:16]
  225. assert data[fingerprint]["description"] == "resolve_orderby"
  226. assert data[fingerprint]["avg(exclusive_time)"] == 15.0
  227. assert data[fingerprint]["count()"] == 2
  228. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D2").hexdigest()[:16]
  229. assert data[fingerprint]["description"] == "resolve_orderby"
  230. assert data[fingerprint]["avg(exclusive_time)"] == 20.0
  231. assert data[fingerprint]["count()"] == 1
  232. @override_options({"indexed-spans.agg-span-waterfall.enable": True})
  233. @mock.patch("sentry.api.endpoints.organization_spans_aggregation.raw_snql_query")
  234. def test_offset_logic(self, mock_query):
  235. mock_query.side_effect = [MOCK_SNUBA_RESPONSE]
  236. with self.feature(self.FEATURES):
  237. response = self.client.get(
  238. self.url,
  239. data={"transaction": "api/0/foo", "statsPeriod": "1d"},
  240. format="json",
  241. )
  242. assert response.data
  243. data = response.data
  244. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  245. assert root_fingerprint in data
  246. assert data[root_fingerprint]["avg(absolute_offset)"] == 0.0
  247. fingerprint = hashlib.md5(b"e238e6c2e2466b07-B").hexdigest()[:16]
  248. assert data[fingerprint]["avg(absolute_offset)"] == 30.0
  249. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C").hexdigest()[:16]
  250. assert data[fingerprint]["avg(absolute_offset)"] == 35.0
  251. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D").hexdigest()[:16]
  252. assert data[fingerprint]["avg(absolute_offset)"] == 53.5
  253. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D2").hexdigest()[:16]
  254. assert data[fingerprint]["avg(absolute_offset)"] == 1075.0
  255. @override_options({"indexed-spans.agg-span-waterfall.enable": True})
  256. @mock.patch("sentry.api.endpoints.organization_spans_aggregation.raw_snql_query")
  257. def test_null_group_fallback(self, mock_query):
  258. mock_query.side_effect = [MOCK_SNUBA_RESPONSE]
  259. with self.feature(self.FEATURES):
  260. response = self.client.get(
  261. self.url,
  262. data={"transaction": "api/0/foo", "statsPeriod": "1d"},
  263. format="json",
  264. )
  265. assert response.data
  266. data = response.data
  267. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-discover.snql").hexdigest()[:16]
  268. assert root_fingerprint in data
  269. assert data[root_fingerprint]["description"] == ""
  270. assert data[root_fingerprint]["count()"] == 2
  271. @override_options({"indexed-spans.agg-span-waterfall.enable": True})
  272. @mock.patch("sentry.api.endpoints.organization_spans_aggregation.raw_snql_query")
  273. def test_http_method_filter(self, mock_query):
  274. with self.feature(self.FEATURES):
  275. self.client.get(
  276. self.url,
  277. data={"transaction": "api/0/foo", "http.method": "GET", "statsPeriod": "1d"},
  278. format="json",
  279. )
  280. assert (
  281. Condition(
  282. lhs=Function(
  283. function="ifNull",
  284. parameters=[
  285. Column(
  286. name="sentry_tags[transaction.method]",
  287. ),
  288. "",
  289. ],
  290. alias=None,
  291. ),
  292. op=Op.EQ,
  293. rhs="GET",
  294. )
  295. in mock_query.mock_calls[0].args[0].query.where
  296. )
  297. class OrganizationNodestoreSpansAggregationTest(APITestCase, SnubaTestCase):
  298. url_name = "sentry-api-0-organization-spans-aggregation"
  299. FEATURES = [
  300. "organizations:insights-initial-modules",
  301. "organizations:performance-view",
  302. ]
  303. def get_start_end(self, duration):
  304. return self.day_ago, self.day_ago + timedelta(milliseconds=duration)
  305. def create_event(
  306. self,
  307. trace,
  308. transaction,
  309. spans,
  310. parent_span_id,
  311. project_id,
  312. tags=None,
  313. duration=4000,
  314. span_id=None,
  315. measurements=None,
  316. trace_context=None,
  317. environment=None,
  318. **kwargs,
  319. ):
  320. start, end = self.get_start_end(duration)
  321. data = load_data(
  322. "transaction",
  323. trace=trace,
  324. spans=spans,
  325. timestamp=end,
  326. start_timestamp=start,
  327. trace_context=trace_context,
  328. )
  329. data["transaction"] = transaction
  330. data["contexts"]["trace"]["parent_span_id"] = parent_span_id
  331. if span_id:
  332. data["contexts"]["trace"]["span_id"] = span_id
  333. if measurements:
  334. for key, value in measurements.items():
  335. data["measurements"][key]["value"] = value
  336. if tags is not None:
  337. data["tags"] = tags
  338. if environment is not None:
  339. data["environment"] = environment
  340. with self.feature(self.FEATURES):
  341. return self.store_event(data, project_id=project_id, assert_no_errors=False, **kwargs)
  342. def setUp(self):
  343. super().setUp()
  344. self.login_as(user=self.user)
  345. self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
  346. self.span_ids_event_1 = dict(
  347. zip(["A", "B", "C", "D", "E"], [uuid4().hex[:16] for _ in range(5)])
  348. )
  349. self.trace_id_1 = uuid4().hex
  350. self.root_event_1 = self.create_event(
  351. trace=self.trace_id_1,
  352. trace_context={
  353. "trace_id": self.trace_id_1,
  354. "span_id": self.span_ids_event_1["A"],
  355. "exclusive_time": 100,
  356. },
  357. transaction="api/0/foo",
  358. spans=[
  359. {
  360. "same_process_as_parent": True,
  361. "op": "db",
  362. "description": "connect",
  363. "span_id": self.span_ids_event_1["B"],
  364. "trace_id": self.trace_id_1,
  365. "parent_span_id": self.span_ids_event_1["A"],
  366. "exclusive_time": 50.0,
  367. "data": {
  368. "duration": 0.050,
  369. "offset": 0.050,
  370. "span.group": "B",
  371. "span.description": "connect",
  372. },
  373. "sentry_tags": {
  374. "description": "connect",
  375. },
  376. },
  377. {
  378. "same_process_as_parent": True,
  379. "op": "discover.endpoint",
  380. "description": "resolve_conditions",
  381. "span_id": self.span_ids_event_1["C"],
  382. "trace_id": self.trace_id_1,
  383. "parent_span_id": self.span_ids_event_1["A"],
  384. "exclusive_time": 10,
  385. "data": {
  386. "duration": 0.00,
  387. "offset": 0.055,
  388. "span.group": "C",
  389. "span.description": "connect",
  390. },
  391. "sentry_tags": {
  392. "description": "connect",
  393. },
  394. },
  395. {
  396. "same_process_as_parent": True,
  397. "op": "discover.snql",
  398. "description": "resolve_orderby",
  399. "span_id": self.span_ids_event_1["D"],
  400. "trace_id": self.trace_id_1,
  401. "parent_span_id": self.span_ids_event_1["C"],
  402. "exclusive_time": 20,
  403. "data": {
  404. "duration": 0.00,
  405. "offset": 0.057,
  406. "span.group": "D",
  407. "span.description": "resolve_orderby",
  408. },
  409. "sentry_tags": {
  410. "description": "resolve_orderby",
  411. },
  412. },
  413. {
  414. "same_process_as_parent": True,
  415. "op": "discover.snql",
  416. "description": "resolve_columns",
  417. "span_id": self.span_ids_event_1["E"],
  418. "trace_id": self.trace_id_1,
  419. "parent_span_id": self.span_ids_event_1["C"],
  420. "exclusive_time": 20,
  421. "data": {
  422. "duration": 0.00,
  423. "offset": 0.057,
  424. "span.description": "resolve_columns",
  425. },
  426. },
  427. ],
  428. parent_span_id=None,
  429. project_id=self.project.id,
  430. duration=1000,
  431. environment="production",
  432. )
  433. self.span_ids_event_2 = dict(
  434. zip(["A", "B", "C", "D", "D2", "E"], [uuid4().hex[:16] for _ in range(6)])
  435. )
  436. self.trace_id_2 = uuid4().hex
  437. self.root_event_2 = self.create_event(
  438. trace=self.trace_id_2,
  439. trace_context={
  440. "trace_id": self.trace_id_2,
  441. "span_id": self.span_ids_event_2["A"],
  442. "exclusive_time": 100,
  443. },
  444. transaction="api/0/foo",
  445. spans=[
  446. {
  447. "same_process_as_parent": True,
  448. "op": "db",
  449. "description": "connect",
  450. "span_id": self.span_ids_event_2["B"],
  451. "trace_id": self.trace_id_2,
  452. "parent_span_id": self.span_ids_event_2["A"],
  453. "exclusive_time": 50.0,
  454. "data": {
  455. "duration": 0.010,
  456. "offset": 0.010,
  457. "span.group": "B",
  458. "span.description": "connect",
  459. },
  460. "sentry_tags": {
  461. "description": "connect",
  462. },
  463. },
  464. {
  465. "same_process_as_parent": True,
  466. "op": "discover.endpoint",
  467. "description": "resolve_conditions",
  468. "span_id": self.span_ids_event_2["C"],
  469. "trace_id": self.trace_id_2,
  470. "parent_span_id": self.span_ids_event_2["A"],
  471. "exclusive_time": 10,
  472. "data": {
  473. "duration": 0.00,
  474. "offset": 0.015,
  475. "span.group": "C",
  476. "span.description": "connect",
  477. },
  478. "sentry_tags": {
  479. "description": "connect",
  480. },
  481. },
  482. {
  483. "same_process_as_parent": True,
  484. "op": "discover.snql",
  485. "description": "resolve_orderby",
  486. "span_id": self.span_ids_event_2["D"],
  487. "trace_id": self.trace_id_2,
  488. "parent_span_id": self.span_ids_event_2["C"],
  489. "exclusive_time": 10,
  490. "data": {
  491. "duration": 0.00,
  492. "offset": 0.050,
  493. "span.group": "D",
  494. "span.description": "resolve_orderby",
  495. },
  496. "sentry_tags": {
  497. "description": "resolve_orderby",
  498. },
  499. },
  500. {
  501. "same_process_as_parent": True,
  502. "op": "discover.snql",
  503. "description": "resolve_orderby",
  504. "span_id": self.span_ids_event_2["D2"],
  505. "trace_id": self.trace_id_2,
  506. "parent_span_id": self.span_ids_event_2["C"],
  507. "exclusive_time": 20,
  508. "data": {
  509. "duration": 0.00,
  510. "offset": 1.055,
  511. "span.group": "D",
  512. "span.description": "resolve_orderby",
  513. },
  514. "sentry_tags": {
  515. "description": "resolve_orderby",
  516. },
  517. },
  518. {
  519. "same_process_as_parent": True,
  520. "op": "discover.snql",
  521. "description": "resolve_columns",
  522. "span_id": self.span_ids_event_2["E"],
  523. "trace_id": self.trace_id_2,
  524. "parent_span_id": self.span_ids_event_2["C"],
  525. "exclusive_time": 20,
  526. "data": {
  527. "duration": 0.00,
  528. "offset": 0.057,
  529. "span.description": "resolve_columns",
  530. },
  531. },
  532. ],
  533. parent_span_id=None,
  534. project_id=self.project.id,
  535. duration=700,
  536. environment="development",
  537. )
  538. self.url = reverse(
  539. self.url_name,
  540. kwargs={"organization_id_or_slug": self.project.organization.slug},
  541. )
  542. @django_db_all
  543. def test_without_flag(self):
  544. response = self.client.get(
  545. self.url,
  546. data={"transaction": "api/0/foo"},
  547. format="json",
  548. )
  549. assert response.status_code == 404
  550. @django_db_all
  551. def test_simple(self):
  552. with self.feature(self.FEATURES):
  553. response = self.client.get(
  554. self.url,
  555. data={"transaction": "api/0/foo"},
  556. format="json",
  557. )
  558. assert response.data
  559. data = response.data
  560. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  561. assert root_fingerprint in data
  562. assert data[root_fingerprint]["count()"] == 2
  563. assert data[root_fingerprint]["description"] == "api/0/foo"
  564. assert round(data[root_fingerprint]["avg(duration)"]) == 850
  565. assert data[root_fingerprint]["samples"] == {
  566. (
  567. self.root_event_1.event_id,
  568. self.span_ids_event_1["A"],
  569. ),
  570. (
  571. self.root_event_2.event_id,
  572. self.span_ids_event_2["A"],
  573. ),
  574. }
  575. assert data[root_fingerprint]["sample_spans"] == [
  576. {
  577. "transaction": self.root_event_1.event_id,
  578. "timestamp": self.root_event_1.data["start_timestamp"],
  579. "span": self.span_ids_event_1["A"],
  580. "trace": self.root_event_1.data["contexts"]["trace"]["trace_id"],
  581. },
  582. {
  583. "transaction": self.root_event_2.event_id,
  584. "timestamp": self.root_event_2.data["start_timestamp"],
  585. "span": self.span_ids_event_2["A"],
  586. "trace": self.root_event_2.data["contexts"]["trace"]["trace_id"],
  587. },
  588. ]
  589. fingerprint = hashlib.md5(b"e238e6c2e2466b07-B").hexdigest()[:16]
  590. assert data[fingerprint]["description"] == "connect"
  591. assert round(data[fingerprint]["avg(duration)"]) == 30
  592. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D").hexdigest()[:16]
  593. assert data[fingerprint]["description"] == "resolve_orderby"
  594. assert data[fingerprint]["avg(exclusive_time)"] == 15.0
  595. assert data[fingerprint]["count()"] == 2
  596. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D2").hexdigest()[:16]
  597. assert data[fingerprint]["description"] == "resolve_orderby"
  598. assert data[fingerprint]["avg(exclusive_time)"] == 20.0
  599. assert data[fingerprint]["count()"] == 1
  600. @django_db_all
  601. def test_offset_logic(self):
  602. with self.feature(self.FEATURES):
  603. response = self.client.get(
  604. self.url,
  605. data={"transaction": "api/0/foo"},
  606. format="json",
  607. )
  608. assert response.data
  609. data = response.data
  610. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  611. assert root_fingerprint in data
  612. assert data[root_fingerprint]["avg(absolute_offset)"] == 0.0
  613. fingerprint = hashlib.md5(b"e238e6c2e2466b07-B").hexdigest()[:16]
  614. assert data[fingerprint]["avg(absolute_offset)"] == 30.0
  615. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C").hexdigest()[:16]
  616. assert data[fingerprint]["avg(absolute_offset)"] == 35.0
  617. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D").hexdigest()[:16]
  618. assert data[fingerprint]["avg(absolute_offset)"] == 53.5
  619. fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-D2").hexdigest()[:16]
  620. assert data[fingerprint]["avg(absolute_offset)"] == 1075.0
  621. @django_db_all
  622. def test_null_group_fallback(self):
  623. with self.feature(self.FEATURES):
  624. response = self.client.get(
  625. self.url,
  626. data={"transaction": "api/0/foo"},
  627. format="json",
  628. )
  629. assert response.data
  630. data = response.data
  631. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07-C-discover.snql").hexdigest()[:16]
  632. assert root_fingerprint in data
  633. assert data[root_fingerprint]["description"] == ""
  634. assert data[root_fingerprint]["count()"] == 2
  635. @django_db_all
  636. def test_http_method_filter(self):
  637. with self.feature(self.FEATURES):
  638. response = self.client.get(
  639. self.url,
  640. data={"transaction": "api/0/foo", "http.method": "GET"},
  641. format="json",
  642. )
  643. assert response.data
  644. data = response.data
  645. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  646. assert root_fingerprint in data
  647. assert data[root_fingerprint]["count()"] == 2
  648. with self.feature(self.FEATURES):
  649. response = self.client.get(
  650. self.url,
  651. data={"transaction": "api/0/foo", "http.method": "POST"},
  652. format="json",
  653. )
  654. assert response.data == {}
  655. @django_db_all
  656. def test_environment_filter(self):
  657. with self.feature(self.FEATURES):
  658. response = self.client.get(
  659. self.url,
  660. data={
  661. "transaction": "api/0/foo",
  662. "environment": "production",
  663. },
  664. format="json",
  665. )
  666. assert response.data
  667. data = response.data
  668. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  669. assert root_fingerprint in data
  670. assert data[root_fingerprint]["count()"] == 1
  671. with self.feature(self.FEATURES):
  672. response = self.client.get(
  673. self.url,
  674. data={
  675. "transaction": "api/0/foo",
  676. "environment": ["production", "development"],
  677. "forceNodestore": "true",
  678. },
  679. format="json",
  680. )
  681. assert response.data
  682. data = response.data
  683. root_fingerprint = hashlib.md5(b"e238e6c2e2466b07").hexdigest()[:16]
  684. assert root_fingerprint in data
  685. assert data[root_fingerprint]["count()"] == 2