test_organization_events_trace.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  1. from uuid import uuid4
  2. from datetime import timedelta
  3. from django.core.urlresolvers import reverse
  4. from sentry.models import Group
  5. from sentry.utils.samples import load_data
  6. from sentry.testutils import APITestCase, SnubaTestCase
  7. from sentry.testutils.helpers.datetime import before_now, iso_format
  8. class OrganizationEventsTraceEndpointBase(APITestCase, SnubaTestCase):
  9. FEATURES = [
  10. "organizations:trace-view-quick",
  11. "organizations:trace-view-summary",
  12. "organizations:global-views",
  13. ]
  14. def get_start_end(self, duration):
  15. start = before_now(minutes=1, milliseconds=duration)
  16. return start, start + timedelta(milliseconds=duration)
  17. def create_event(
  18. self,
  19. trace,
  20. transaction,
  21. spans,
  22. parent_span_id,
  23. project_id,
  24. duration=4000,
  25. span_id=None,
  26. measurements=None,
  27. ):
  28. start, end = self.get_start_end(duration)
  29. data = load_data(
  30. "transaction",
  31. trace=trace,
  32. spans=spans,
  33. timestamp=end,
  34. start_timestamp=start,
  35. )
  36. data["transaction"] = transaction
  37. data["contexts"]["trace"]["parent_span_id"] = parent_span_id
  38. if span_id:
  39. data["contexts"]["trace"]["span_id"] = span_id
  40. if measurements:
  41. for key, value in measurements.items():
  42. data["measurements"][key]["value"] = value
  43. return self.store_event(data, project_id=project_id)
  44. def get_error_url(self, error):
  45. return Group.objects.get(id=error.group_id).get_absolute_url()
  46. def setUp(self):
  47. """
  48. Span structure:
  49. root
  50. gen1-0
  51. gen2-0
  52. gen3-0
  53. gen1-1
  54. gen2-1
  55. gen1-2
  56. gen2-2
  57. """
  58. super().setUp()
  59. self.login_as(user=self.user)
  60. self.day_ago = before_now(days=1).replace(hour=10, minute=0, second=0, microsecond=0)
  61. self.root_span_ids = [uuid4().hex[:16] for _ in range(3)]
  62. self.trace_id = uuid4().hex
  63. self.root_event = self.create_event(
  64. trace=self.trace_id,
  65. transaction="root",
  66. spans=[
  67. {
  68. "same_process_as_parent": True,
  69. "op": "http",
  70. "description": f"GET gen1-{i}",
  71. "span_id": root_span_id,
  72. "trace_id": self.trace_id,
  73. }
  74. for i, root_span_id in enumerate(self.root_span_ids)
  75. ],
  76. measurements={
  77. "lcp": 1000,
  78. "fcp": 750,
  79. },
  80. parent_span_id=None,
  81. project_id=self.project.id,
  82. duration=3000,
  83. )
  84. # First Generation
  85. self.gen1_span_ids = [uuid4().hex[:16] for _ in range(3)]
  86. self.gen1_project = self.create_project(organization=self.organization)
  87. self.gen1_events = [
  88. self.create_event(
  89. trace=self.trace_id,
  90. transaction=f"/transaction/gen1-{i}",
  91. spans=[
  92. {
  93. "same_process_as_parent": True,
  94. "op": "http",
  95. "description": f"GET gen2-{i}",
  96. "span_id": gen1_span_id,
  97. "trace_id": self.trace_id,
  98. }
  99. ],
  100. parent_span_id=root_span_id,
  101. project_id=self.gen1_project.id,
  102. duration=2000,
  103. )
  104. for i, (root_span_id, gen1_span_id) in enumerate(
  105. zip(self.root_span_ids, self.gen1_span_ids)
  106. )
  107. ]
  108. # Second Generation
  109. self.gen2_span_ids = [uuid4().hex[:16] for _ in range(3)]
  110. self.gen2_project = self.create_project(organization=self.organization)
  111. self.gen2_span_id = uuid4().hex[:16]
  112. self.gen2_events = [
  113. self.create_event(
  114. trace=self.trace_id,
  115. transaction=f"/transaction/gen2-{i}",
  116. spans=[
  117. {
  118. "same_process_as_parent": True,
  119. "op": "http",
  120. "description": f"GET gen3-{i}" if i == 0 else f"SPAN gen3-{i}",
  121. "span_id": gen2_span_id,
  122. "trace_id": self.trace_id,
  123. }
  124. ],
  125. parent_span_id=gen1_span_id,
  126. span_id=self.gen2_span_id if i == 0 else None,
  127. project_id=self.gen2_project.id,
  128. duration=1000,
  129. )
  130. for i, (gen1_span_id, gen2_span_id) in enumerate(
  131. zip(self.gen1_span_ids, self.gen2_span_ids)
  132. )
  133. ]
  134. # Third generation
  135. self.gen3_project = self.create_project(organization=self.organization)
  136. self.gen3_event = self.create_event(
  137. trace=self.trace_id,
  138. transaction="/transaction/gen3-0",
  139. spans=[],
  140. project_id=self.gen3_project.id,
  141. parent_span_id=self.gen2_span_id,
  142. duration=500,
  143. )
  144. self.url = reverse(
  145. self.url_name,
  146. kwargs={"organization_slug": self.project.organization.slug, "trace_id": self.trace_id},
  147. )
  148. class OrganizationEventsTraceLightEndpointTest(OrganizationEventsTraceEndpointBase):
  149. url_name = "sentry-api-0-organization-events-trace-light"
  150. def test_no_projects(self):
  151. user = self.create_user()
  152. org = self.create_organization(owner=user)
  153. self.login_as(user=user)
  154. url = reverse(
  155. self.url_name,
  156. kwargs={"organization_slug": org.slug, "trace_id": uuid4().hex},
  157. )
  158. with self.feature(self.FEATURES):
  159. response = self.client.get(
  160. url,
  161. format="json",
  162. )
  163. assert response.status_code == 404, response.content
  164. def test_bad_ids(self):
  165. # Fake event id
  166. with self.feature(self.FEATURES):
  167. response = self.client.get(
  168. self.url,
  169. data={"event_id": uuid4().hex},
  170. format="json",
  171. )
  172. assert response.status_code == 404, response.content
  173. # Fake trace id
  174. self.url = reverse(
  175. "sentry-api-0-organization-events-trace-light",
  176. kwargs={"organization_slug": self.project.organization.slug, "trace_id": uuid4().hex},
  177. )
  178. with self.feature(self.FEATURES):
  179. response = self.client.get(
  180. self.url,
  181. data={"event_id": self.root_event.event_id},
  182. format="json",
  183. )
  184. assert response.status_code == 404, response.content
  185. def test_no_roots(self):
  186. no_root_trace = uuid4().hex
  187. event = self.create_event(
  188. trace=no_root_trace,
  189. transaction="/not_root/but_only_transaction",
  190. spans=[],
  191. parent_span_id=uuid4().hex[:16],
  192. project_id=self.project.id,
  193. )
  194. self.url = reverse(
  195. "sentry-api-0-organization-events-trace-light",
  196. kwargs={"organization_slug": self.project.organization.slug, "trace_id": no_root_trace},
  197. )
  198. with self.feature(self.FEATURES):
  199. response = self.client.get(
  200. self.url,
  201. data={"event_id": event.event_id},
  202. format="json",
  203. )
  204. assert response.status_code == 204, response.content
  205. def test_multiple_roots(self):
  206. self.create_event(
  207. trace=self.trace_id,
  208. transaction="/second_root",
  209. spans=[],
  210. parent_span_id=None,
  211. project_id=self.project.id,
  212. )
  213. with self.feature(self.FEATURES):
  214. response = self.client.get(
  215. self.url,
  216. data={"event_id": self.root_event.event_id},
  217. format="json",
  218. )
  219. assert response.status_code == 200, response.content
  220. def test_root_event(self):
  221. root_event_id = self.root_event.event_id
  222. with self.feature(self.FEATURES):
  223. response = self.client.get(
  224. self.url,
  225. data={"event_id": root_event_id, "project": -1},
  226. format="json",
  227. )
  228. assert response.status_code == 200, response.content
  229. assert len(response.data) == 4
  230. events = {item["event_id"]: item for item in response.data}
  231. assert root_event_id in events
  232. event = events[root_event_id]
  233. assert event["generation"] == 0
  234. assert event["parent_event_id"] is None
  235. assert event["parent_span_id"] is None
  236. for i, child_event in enumerate(self.gen1_events):
  237. child_event_id = child_event.event_id
  238. assert child_event_id in events
  239. event = events[child_event_id]
  240. assert event["generation"] == 1
  241. assert event["parent_event_id"] == root_event_id
  242. assert event["parent_span_id"] == self.root_span_ids[i]
  243. def test_direct_parent_with_children(self):
  244. root_event_id = self.root_event.event_id
  245. current_event = self.gen1_events[0].event_id
  246. child_event_id = self.gen2_events[0].event_id
  247. with self.feature(self.FEATURES):
  248. response = self.client.get(
  249. self.url,
  250. data={"event_id": current_event, "project": -1},
  251. format="json",
  252. )
  253. assert response.status_code == 200, response.content
  254. assert len(response.data) == 3
  255. events = {item["event_id"]: item for item in response.data}
  256. assert root_event_id in events
  257. event = events[root_event_id]
  258. assert event["generation"] == 0
  259. assert event["parent_event_id"] is None
  260. assert event["parent_span_id"] is None
  261. assert current_event in events
  262. event = events[current_event]
  263. assert event["generation"] == 1
  264. assert event["parent_event_id"] == root_event_id
  265. assert event["parent_span_id"] == self.root_span_ids[0]
  266. assert child_event_id in events
  267. event = events[child_event_id]
  268. assert event["generation"] == 2
  269. assert event["parent_event_id"] == current_event
  270. assert event["parent_span_id"] == self.gen1_span_ids[0]
  271. def test_second_generation_with_children(self):
  272. root_event_id = self.root_event.event_id
  273. current_event = self.gen2_events[0].event_id
  274. child_event_id = self.gen3_event.event_id
  275. with self.feature(self.FEATURES):
  276. response = self.client.get(
  277. self.url,
  278. data={"event_id": current_event, "project": -1},
  279. format="json",
  280. )
  281. assert response.status_code == 200, response.content
  282. assert len(response.data) == 3
  283. events = {item["event_id"]: item for item in response.data}
  284. assert root_event_id in events
  285. event = events[root_event_id]
  286. assert event["generation"] == 0
  287. assert event["parent_event_id"] is None
  288. assert event["parent_span_id"] is None
  289. assert current_event in events
  290. event = events[current_event]
  291. # Parent/generation is unknown in this case
  292. assert event["generation"] is None
  293. assert event["parent_event_id"] is None
  294. # But we still know the parent_span
  295. assert event["parent_span_id"] == self.gen1_span_ids[0]
  296. assert child_event_id in events
  297. event = events[child_event_id]
  298. assert event["generation"] is None
  299. assert event["parent_event_id"] == current_event
  300. assert event["parent_span_id"] == self.gen2_span_id
  301. def test_third_generation_no_children(self):
  302. root_event_id = self.root_event.event_id
  303. current_event = self.gen3_event.event_id
  304. with self.feature(self.FEATURES):
  305. response = self.client.get(
  306. self.url,
  307. data={"event_id": current_event, "project": -1},
  308. format="json",
  309. )
  310. assert response.status_code == 200, response.content
  311. assert len(response.data) == 2
  312. events = {item["event_id"]: item for item in response.data}
  313. assert root_event_id in events
  314. event = events[root_event_id]
  315. assert event["generation"] == 0
  316. assert event["parent_event_id"] is None
  317. assert event["parent_span_id"] is None
  318. assert current_event in events
  319. event = events[current_event]
  320. assert event["generation"] is None
  321. # Parent is unknown in this case
  322. assert event["parent_event_id"] is None
  323. # But we still know the parent_span
  324. assert event["parent_span_id"] == self.gen2_span_id
  325. def test_sibling_transactions(self):
  326. """ More than one transaction can share a parent_span_id """
  327. gen3_event_siblings = [
  328. self.create_event(
  329. trace=self.trace_id,
  330. transaction="/transaction/gen3-1",
  331. spans=[],
  332. project_id=self.create_project(organization=self.organization).id,
  333. parent_span_id=self.gen2_span_ids[1],
  334. duration=500,
  335. ).event_id,
  336. self.create_event(
  337. trace=self.trace_id,
  338. transaction="/transaction/gen3-2",
  339. spans=[],
  340. project_id=self.create_project(organization=self.organization).id,
  341. parent_span_id=self.gen2_span_ids[1],
  342. duration=500,
  343. ).event_id,
  344. ]
  345. current_event = self.gen2_events[1].event_id
  346. with self.feature(self.FEATURES):
  347. response = self.client.get(
  348. self.url,
  349. data={"event_id": current_event, "project": -1},
  350. format="json",
  351. )
  352. assert len(response.data) == 4
  353. events = {item["event_id"]: item for item in response.data}
  354. for child_event_id in gen3_event_siblings:
  355. assert child_event_id in events
  356. event = events[child_event_id]
  357. assert event["generation"] is None
  358. assert event["parent_event_id"] == current_event
  359. assert event["parent_span_id"] == self.gen2_span_ids[1]
  360. def test_with_error_event(self):
  361. root_event_id = self.root_event.event_id
  362. current_transaction_event = self.gen1_events[0].event_id
  363. start, _ = self.get_start_end(1000)
  364. error_data = load_data(
  365. "javascript",
  366. timestamp=start,
  367. )
  368. error_data["contexts"]["trace"] = {
  369. "type": "trace",
  370. "trace_id": self.trace_id,
  371. "span_id": self.gen1_span_ids[0],
  372. }
  373. error_data["tags"] = [["transaction", "/transaction/gen1-0"]]
  374. error = self.store_event(error_data, project_id=self.gen1_project.id)
  375. def assertions(response):
  376. assert response.status_code == 200, response.content
  377. assert len(response.data) == 3
  378. events = {item["event_id"]: item for item in response.data}
  379. assert root_event_id in events
  380. event = events[root_event_id]
  381. assert event["generation"] == 0
  382. assert event["parent_event_id"] is None
  383. assert event["parent_span_id"] is None
  384. assert len(event["errors"]) == 0
  385. assert current_transaction_event in events
  386. event = events[current_transaction_event]
  387. assert event["generation"] == 1
  388. assert event["parent_event_id"] == root_event_id
  389. assert event["parent_span_id"] == self.root_span_ids[0]
  390. assert len(event["errors"]) == 1
  391. assert event["errors"][0]["event_id"] == error.event_id
  392. assert event["errors"][0]["url"] == self.get_error_url(error)
  393. with self.feature(self.FEATURES):
  394. response = self.client.get(
  395. self.url,
  396. data={"event_id": error.event_id, "project": -1},
  397. format="json",
  398. )
  399. assertions(response)
  400. with self.feature(self.FEATURES):
  401. response = self.client.get(
  402. self.url,
  403. data={"event_id": current_transaction_event, "project": -1},
  404. format="json",
  405. )
  406. assertions(response)
  407. class OrganizationEventsTraceEndpointTest(OrganizationEventsTraceEndpointBase):
  408. url_name = "sentry-api-0-organization-events-trace"
  409. def assert_event(self, result, event_data, message):
  410. assert result["event_id"] == event_data.event_id, message
  411. assert result["timestamp"] == event_data.data["timestamp"], message
  412. assert result["start_timestamp"] == event_data.data["start_timestamp"], message
  413. def assert_trace_data(self, root, gen2_no_children=True):
  414. """ see the setUp docstring for an idea of what the response structure looks like """
  415. self.assert_event(root, self.root_event, "root")
  416. assert root["parent_event_id"] is None
  417. assert root["parent_span_id"] is None
  418. assert root["generation"] == 0
  419. assert root["transaction.duration"] == 3000
  420. assert len(root["children"]) == 3
  421. for i, gen1 in enumerate(root["children"]):
  422. self.assert_event(gen1, self.gen1_events[i], f"gen1_{i}")
  423. assert gen1["parent_event_id"] == self.root_event.event_id
  424. assert gen1["parent_span_id"] == self.root_span_ids[i]
  425. assert gen1["generation"] == 1
  426. assert gen1["transaction.duration"] == 2000
  427. assert len(gen1["children"]) == 1
  428. gen2 = gen1["children"][0]
  429. self.assert_event(gen2, self.gen2_events[i], f"gen2_{i}")
  430. assert gen2["parent_event_id"] == self.gen1_events[i].event_id
  431. assert gen2["parent_span_id"] == self.gen1_span_ids[i]
  432. assert gen2["generation"] == 2
  433. assert gen2["transaction.duration"] == 1000
  434. # Only the first gen2 descendent has a child
  435. if i == 0:
  436. assert len(gen2["children"]) == 1
  437. gen3 = gen2["children"][0]
  438. self.assert_event(gen3, self.gen3_event, f"gen3_{i}")
  439. assert gen3["parent_event_id"] == self.gen2_events[i].event_id
  440. assert gen3["parent_span_id"] == self.gen2_span_id
  441. assert gen3["generation"] == 3
  442. assert gen3["transaction.duration"] == 500
  443. assert len(gen3["children"]) == 0
  444. elif gen2_no_children:
  445. assert len(gen2["children"]) == 0
  446. def test_no_projects(self):
  447. user = self.create_user()
  448. org = self.create_organization(owner=user)
  449. self.login_as(user=user)
  450. url = reverse(
  451. self.url_name,
  452. kwargs={"organization_slug": org.slug, "trace_id": uuid4().hex},
  453. )
  454. with self.feature(self.FEATURES):
  455. response = self.client.get(
  456. url,
  457. format="json",
  458. )
  459. assert response.status_code == 404, response.content
  460. def test_simple(self):
  461. with self.feature(self.FEATURES):
  462. response = self.client.get(
  463. self.url,
  464. data={"project": -1},
  465. format="json",
  466. )
  467. assert response.status_code == 200, response.content
  468. self.assert_trace_data(response.data[0])
  469. # We shouldn't have detailed fields here
  470. assert "transaction.status" not in response.data[0]
  471. assert "tags" not in response.data[0]
  472. assert "measurements" not in response.data[0]
  473. def test_detailed_trace(self):
  474. with self.feature(self.FEATURES):
  475. response = self.client.get(
  476. self.url,
  477. data={"project": -1, "detailed": 1},
  478. format="json",
  479. )
  480. assert response.status_code == 200, response.content
  481. self.assert_trace_data(response.data[0])
  482. root = response.data[0]
  483. assert root["transaction.status"] == "ok"
  484. root_tags = {tag["key"]: tag["value"] for tag in root["tags"]}
  485. for [key, value] in self.root_event.tags:
  486. if not key.startswith("sentry:"):
  487. assert root_tags[key] == value, f"tags - {key}"
  488. else:
  489. assert root_tags[key[7:]] == value, f"tags - {key}"
  490. assert root["measurements"]["lcp"]["value"] == 1000
  491. assert root["measurements"]["fcp"]["value"] == 750
  492. def test_bad_span_loop(self):
  493. """Maliciously create a loop in the span structure
  494. Structure then becomes something like this:
  495. root
  496. gen1-0...
  497. gen1-1
  498. gen2-1
  499. gen3-1
  500. gen_2-1
  501. gen3-1...
  502. """
  503. gen3_loop_event = self.create_event(
  504. trace=self.trace_id,
  505. transaction="/transaction/gen3-1/loop",
  506. spans=[
  507. {
  508. "same_process_as_parent": True,
  509. "op": "http",
  510. "description": "GET gen2-1",
  511. "span_id": self.gen1_span_ids[1],
  512. "trace_id": self.trace_id,
  513. }
  514. ],
  515. parent_span_id=self.gen2_span_ids[1],
  516. project_id=self.project.id,
  517. )
  518. with self.feature(self.FEATURES):
  519. response = self.client.get(
  520. self.url,
  521. data={"project": -1},
  522. format="json",
  523. )
  524. assert response.status_code == 200, response.content
  525. # Should be the same as the simple testcase
  526. self.assert_trace_data(response.data[0], gen2_no_children=False)
  527. # The difference is that gen3-1 should exist with no children
  528. gen2_1 = response.data[0]["children"][1]["children"][0]
  529. assert len(gen2_1["children"]) == 1
  530. gen3_1 = gen2_1["children"][0]
  531. assert gen3_1["event_id"] == gen3_loop_event.event_id
  532. # We didn't even try to start the loop of spans
  533. assert len(gen3_1["children"]) == 0
  534. def test_sibling_transactions(self):
  535. """ More than one transaction can share a parent_span_id """
  536. gen3_event_siblings = [
  537. self.create_event(
  538. trace=self.trace_id,
  539. transaction="/transaction/gen3-1",
  540. spans=[],
  541. project_id=self.create_project(organization=self.organization).id,
  542. parent_span_id=self.gen2_span_ids[1],
  543. duration=500,
  544. ).event_id,
  545. self.create_event(
  546. trace=self.trace_id,
  547. transaction="/transaction/gen3-2",
  548. spans=[],
  549. project_id=self.create_project(organization=self.organization).id,
  550. parent_span_id=self.gen2_span_ids[1],
  551. duration=500,
  552. ).event_id,
  553. ]
  554. with self.feature(self.FEATURES):
  555. response = self.client.get(
  556. self.url,
  557. data={"project": -1},
  558. format="json",
  559. )
  560. assert response.status_code == 200, response.content
  561. # Should be the same as the simple testcase, but skip checking gen2 children
  562. self.assert_trace_data(response.data[0], gen2_no_children=False)
  563. gen2_parent = response.data[0]["children"][1]["children"][0]
  564. assert len(gen2_parent["children"]) == 2
  565. for child in gen2_parent["children"]:
  566. assert child["event_id"] in gen3_event_siblings
  567. def test_with_orphan_siblings(self):
  568. parent_span_id = uuid4().hex[:16]
  569. root_event = self.create_event(
  570. trace=self.trace_id,
  571. transaction="/orphan/root",
  572. spans=[],
  573. # Some random id so its separated from the rest of the trace
  574. parent_span_id=parent_span_id,
  575. project_id=self.project.id,
  576. duration=1000,
  577. )
  578. root_sibling_event = self.create_event(
  579. trace=self.trace_id,
  580. transaction="/orphan/root-sibling",
  581. spans=[],
  582. parent_span_id=parent_span_id,
  583. project_id=self.project.id,
  584. duration=1000,
  585. )
  586. with self.feature(self.FEATURES):
  587. response = self.client.get(
  588. self.url,
  589. data={"project": -1},
  590. format="json",
  591. )
  592. assert response.status_code == 200, response.content
  593. assert len(response.data) == 3
  594. # The first item of the response should be the main trace
  595. main, *orphans = response.data
  596. self.assert_trace_data(main)
  597. assert root_event.event_id in [orphan["event_id"] for orphan in orphans]
  598. assert root_sibling_event.event_id in [orphan["event_id"] for orphan in orphans]
  599. def test_with_orphan_trace(self):
  600. orphan_span_ids = {
  601. key: uuid4().hex[:16]
  602. for key in ["root", "root_span", "child", "child_span", "grandchild", "grandchild_span"]
  603. }
  604. # Create the orphan transactions
  605. root_event = self.create_event(
  606. trace=self.trace_id,
  607. transaction="/orphan/root",
  608. spans=[
  609. {
  610. "same_process_as_parent": True,
  611. "op": "http",
  612. "description": "GET gen1 orphan",
  613. "span_id": orphan_span_ids["root_span"],
  614. "trace_id": self.trace_id,
  615. }
  616. ],
  617. # Some random id so its separated from the rest of the trace
  618. parent_span_id=uuid4().hex[:16],
  619. span_id=orphan_span_ids["root"],
  620. project_id=self.project.id,
  621. duration=1000,
  622. )
  623. child_event = self.create_event(
  624. trace=self.trace_id,
  625. transaction="/orphan/child1-0",
  626. spans=[
  627. {
  628. "same_process_as_parent": True,
  629. "op": "http",
  630. "description": "GET gen1 orphan",
  631. "span_id": orphan_span_ids["child_span"],
  632. "trace_id": self.trace_id,
  633. }
  634. ],
  635. # Some random id so its separated from the rest of the trace
  636. parent_span_id=orphan_span_ids["root_span"],
  637. span_id=orphan_span_ids["child"],
  638. project_id=self.gen1_project.id,
  639. duration=500,
  640. )
  641. grandchild_event = self.create_event(
  642. trace=self.trace_id,
  643. transaction="/orphan/grandchild1-0",
  644. spans=[
  645. {
  646. "same_process_as_parent": True,
  647. "op": "http",
  648. "description": "GET gen1 orphan",
  649. "span_id": orphan_span_ids["grandchild_span"],
  650. "trace_id": self.trace_id,
  651. }
  652. ],
  653. # Some random id so its separated from the rest of the trace
  654. parent_span_id=orphan_span_ids["child_span"],
  655. span_id=orphan_span_ids["grandchild"],
  656. project_id=self.gen1_project.id,
  657. duration=1500,
  658. )
  659. with self.feature(self.FEATURES):
  660. response = self.client.get(
  661. self.url,
  662. data={"project": -1},
  663. format="json",
  664. )
  665. assert response.status_code == 200, response.content
  666. assert len(response.data) == 2
  667. # The first item of the response should be the main trace
  668. main, orphans = response.data
  669. self.assert_trace_data(main)
  670. self.assert_event(orphans, root_event, "orphan-root")
  671. assert len(orphans["children"]) == 1
  672. assert orphans["generation"] == 0
  673. child = orphans["children"][0]
  674. self.assert_event(child, child_event, "orphan-child")
  675. assert len(child["children"]) == 1
  676. assert child["generation"] == 1
  677. grandchild = child["children"][0]
  678. self.assert_event(grandchild, grandchild_event, "orphan-grandchild")
  679. assert grandchild["generation"] == 2
  680. def test_with_errors(self):
  681. start, _ = self.get_start_end(1000)
  682. error_data = load_data(
  683. "javascript",
  684. timestamp=start,
  685. )
  686. error_data["contexts"]["trace"] = {
  687. "type": "trace",
  688. "trace_id": self.trace_id,
  689. "span_id": self.gen1_span_ids[0],
  690. }
  691. error = self.store_event(error_data, project_id=self.gen1_project.id)
  692. error1 = self.store_event(error_data, project_id=self.gen1_project.id)
  693. with self.feature(self.FEATURES):
  694. response = self.client.get(
  695. self.url,
  696. data={"project": -1},
  697. format="json",
  698. )
  699. assert response.status_code == 200, response.content
  700. self.assert_trace_data(response.data[0])
  701. gen1_event = response.data[0]["children"][0]
  702. assert len(gen1_event["errors"]) == 2
  703. assert {
  704. "event_id": error.event_id,
  705. "span": self.gen1_span_ids[0],
  706. "project_id": self.gen1_project.id,
  707. "project_slug": self.gen1_project.slug,
  708. "url": self.get_error_url(error),
  709. } in gen1_event["errors"]
  710. assert {
  711. "event_id": error1.event_id,
  712. "span": self.gen1_span_ids[0],
  713. "project_id": self.gen1_project.id,
  714. "project_slug": self.gen1_project.slug,
  715. "url": self.get_error_url(error1),
  716. } in gen1_event["errors"]
  717. def test_with_default(self):
  718. start, _ = self.get_start_end(1000)
  719. default_event = self.store_event(
  720. {
  721. "timestamp": iso_format(start),
  722. "contexts": {
  723. "trace": {
  724. "type": "trace",
  725. "trace_id": self.trace_id,
  726. "span_id": self.root_span_ids[0],
  727. },
  728. },
  729. },
  730. project_id=self.gen1_project.id,
  731. )
  732. with self.feature(self.FEATURES):
  733. response = self.client.get(
  734. self.url,
  735. data={"project": -1},
  736. format="json",
  737. )
  738. assert response.status_code == 200, response.content
  739. self.assert_trace_data(response.data[0])
  740. root_event = response.data[0]
  741. assert len(root_event["errors"]) == 1
  742. assert {
  743. "event_id": default_event.event_id,
  744. "span": self.root_span_ids[0],
  745. "project_id": self.gen1_project.id,
  746. "project_slug": self.gen1_project.slug,
  747. "url": self.get_error_url(default_event),
  748. } in root_event["errors"]