test_organization_events_trace.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. from datetime import timedelta
  2. from uuid import uuid4
  3. from django.core.urlresolvers import reverse, NoReverseMatch
  4. from sentry.testutils import APITestCase, SnubaTestCase
  5. from sentry.testutils.helpers.datetime import before_now, iso_format
  6. from sentry.utils.samples import load_data
  7. class OrganizationEventsTraceEndpointBase(APITestCase, SnubaTestCase):
  8. FEATURES = [
  9. "organizations:trace-view-quick",
  10. "organizations:trace-view-summary",
  11. ]
  12. def get_start_end(self, duration):
  13. start = before_now(minutes=1, milliseconds=duration)
  14. return start, start + timedelta(milliseconds=duration)
  15. def create_event(
  16. self,
  17. trace,
  18. transaction,
  19. spans,
  20. parent_span_id,
  21. project_id,
  22. tags=None,
  23. duration=4000,
  24. span_id=None,
  25. measurements=None,
  26. **kwargs,
  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. if tags is not None:
  44. data["tags"] = tags
  45. return self.store_event(data, project_id=project_id, **kwargs)
  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. # Invalid event id
  174. with self.feature(self.FEATURES):
  175. response = self.client.get(
  176. self.url,
  177. data={"event_id": "not-a-event"},
  178. format="json",
  179. )
  180. assert response.status_code == 400, response.content
  181. # Fake trace id
  182. self.url = reverse(
  183. "sentry-api-0-organization-events-trace-light",
  184. kwargs={"organization_slug": self.project.organization.slug, "trace_id": uuid4().hex},
  185. )
  186. with self.feature(self.FEATURES):
  187. response = self.client.get(
  188. self.url,
  189. data={"event_id": self.root_event.event_id},
  190. format="json",
  191. )
  192. assert response.status_code == 404, response.content
  193. # Invalid trace id
  194. with self.assertRaises(NoReverseMatch):
  195. self.url = reverse(
  196. "sentry-api-0-organization-events-trace-light",
  197. kwargs={
  198. "organization_slug": self.project.organization.slug,
  199. "trace_id": "not-a-trace",
  200. },
  201. )
  202. def test_no_roots(self):
  203. """ Even when there's no root, we return the current event """
  204. no_root_trace = uuid4().hex
  205. parent_span_id = uuid4().hex[:16]
  206. no_root_event = self.create_event(
  207. trace=no_root_trace,
  208. transaction="/not_root/but_only_transaction",
  209. spans=[],
  210. parent_span_id=parent_span_id,
  211. project_id=self.project.id,
  212. )
  213. url = reverse(
  214. "sentry-api-0-organization-events-trace-light",
  215. kwargs={"organization_slug": self.project.organization.slug, "trace_id": no_root_trace},
  216. )
  217. with self.feature(self.FEATURES):
  218. response = self.client.get(
  219. url,
  220. data={"event_id": no_root_event.event_id},
  221. format="json",
  222. )
  223. assert response.status_code == 200, response.content
  224. assert len(response.data) == 1
  225. event = response.data[0]
  226. # Basically know nothing about this event
  227. assert event["generation"] is None
  228. assert event["parent_event_id"] is None
  229. assert event["parent_span_id"] == parent_span_id
  230. assert event["event_id"] == no_root_event.event_id
  231. def test_multiple_roots(self):
  232. self.create_event(
  233. trace=self.trace_id,
  234. transaction="/second_root",
  235. spans=[],
  236. parent_span_id=None,
  237. project_id=self.project.id,
  238. )
  239. with self.feature(self.FEATURES):
  240. response = self.client.get(
  241. self.url,
  242. data={"event_id": self.root_event.event_id},
  243. format="json",
  244. )
  245. assert response.status_code == 200, response.content
  246. def test_root_event(self):
  247. root_event_id = self.root_event.event_id
  248. with self.feature(self.FEATURES):
  249. response = self.client.get(
  250. self.url,
  251. data={"event_id": root_event_id, "project": -1},
  252. format="json",
  253. )
  254. assert response.status_code == 200, response.content
  255. assert len(response.data) == 4
  256. events = {item["event_id"]: item for item in response.data}
  257. assert root_event_id in events
  258. event = events[root_event_id]
  259. assert event["generation"] == 0
  260. assert event["parent_event_id"] is None
  261. assert event["parent_span_id"] is None
  262. for i, child_event in enumerate(self.gen1_events):
  263. child_event_id = child_event.event_id
  264. assert child_event_id in events
  265. event = events[child_event_id]
  266. assert event["generation"] == 1
  267. assert event["parent_event_id"] == root_event_id
  268. assert event["parent_span_id"] == self.root_span_ids[i]
  269. def test_direct_parent_with_children(self):
  270. root_event_id = self.root_event.event_id
  271. current_event = self.gen1_events[0].event_id
  272. child_event_id = self.gen2_events[0].event_id
  273. with self.feature(self.FEATURES):
  274. response = self.client.get(
  275. self.url,
  276. data={"event_id": current_event, "project": -1},
  277. format="json",
  278. )
  279. assert response.status_code == 200, response.content
  280. assert len(response.data) == 3
  281. events = {item["event_id"]: item for item in response.data}
  282. assert root_event_id in events
  283. event = events[root_event_id]
  284. assert event["generation"] == 0
  285. assert event["parent_event_id"] is None
  286. assert event["parent_span_id"] is None
  287. assert current_event in events
  288. event = events[current_event]
  289. assert event["generation"] == 1
  290. assert event["parent_event_id"] == root_event_id
  291. assert event["parent_span_id"] == self.root_span_ids[0]
  292. assert child_event_id in events
  293. event = events[child_event_id]
  294. assert event["generation"] == 2
  295. assert event["parent_event_id"] == current_event
  296. assert event["parent_span_id"] == self.gen1_span_ids[0]
  297. def test_second_generation_with_children(self):
  298. current_event = self.gen2_events[0].event_id
  299. child_event_id = self.gen3_event.event_id
  300. with self.feature(self.FEATURES):
  301. response = self.client.get(
  302. self.url,
  303. data={"event_id": current_event, "project": -1},
  304. format="json",
  305. )
  306. assert response.status_code == 200, response.content
  307. assert len(response.data) == 2
  308. events = {item["event_id"]: item for item in response.data}
  309. assert current_event in events
  310. event = events[current_event]
  311. # Parent/generation is unknown in this case
  312. assert event["generation"] is None
  313. assert event["parent_event_id"] is None
  314. # But we still know the parent_span
  315. assert event["parent_span_id"] == self.gen1_span_ids[0]
  316. assert child_event_id in events
  317. event = events[child_event_id]
  318. assert event["generation"] is None
  319. assert event["parent_event_id"] == current_event
  320. assert event["parent_span_id"] == self.gen2_span_id
  321. def test_third_generation_no_children(self):
  322. current_event = self.gen3_event.event_id
  323. with self.feature(self.FEATURES):
  324. response = self.client.get(
  325. self.url,
  326. data={"event_id": current_event, "project": -1},
  327. format="json",
  328. )
  329. assert response.status_code == 200, response.content
  330. assert len(response.data) == 1
  331. event = response.data[0]
  332. assert event["generation"] is None
  333. # Parent is unknown in this case
  334. assert event["parent_event_id"] is None
  335. # But we still know the parent_span
  336. assert event["parent_span_id"] == self.gen2_span_id
  337. def test_sibling_transactions(self):
  338. """ More than one transaction can share a parent_span_id """
  339. gen3_event_siblings = [
  340. self.create_event(
  341. trace=self.trace_id,
  342. transaction="/transaction/gen3-1",
  343. spans=[],
  344. project_id=self.create_project(organization=self.organization).id,
  345. parent_span_id=self.gen2_span_ids[1],
  346. duration=500,
  347. ).event_id,
  348. self.create_event(
  349. trace=self.trace_id,
  350. transaction="/transaction/gen3-2",
  351. spans=[],
  352. project_id=self.create_project(organization=self.organization).id,
  353. parent_span_id=self.gen2_span_ids[1],
  354. duration=500,
  355. ).event_id,
  356. ]
  357. current_event = self.gen2_events[1].event_id
  358. with self.feature(self.FEATURES):
  359. response = self.client.get(
  360. self.url,
  361. data={"event_id": current_event, "project": -1},
  362. format="json",
  363. )
  364. assert len(response.data) == 3
  365. events = {item["event_id"]: item for item in response.data}
  366. for child_event_id in gen3_event_siblings:
  367. assert child_event_id in events
  368. event = events[child_event_id]
  369. assert event["generation"] is None
  370. assert event["parent_event_id"] == current_event
  371. assert event["parent_span_id"] == self.gen2_span_ids[1]
  372. def test_with_error_event(self):
  373. root_event_id = self.root_event.event_id
  374. current_transaction_event = self.gen1_events[0].event_id
  375. start, _ = self.get_start_end(1000)
  376. error_data = load_data(
  377. "javascript",
  378. timestamp=start,
  379. )
  380. error_data["contexts"]["trace"] = {
  381. "type": "trace",
  382. "trace_id": self.trace_id,
  383. "span_id": self.gen1_span_ids[0],
  384. }
  385. error_data["tags"] = [["transaction", "/transaction/gen1-0"]]
  386. error = self.store_event(error_data, project_id=self.gen1_project.id)
  387. def assertions(response):
  388. assert response.status_code == 200, response.content
  389. assert len(response.data) == 3
  390. events = {item["event_id"]: item for item in response.data}
  391. assert root_event_id in events
  392. event = events[root_event_id]
  393. assert event["generation"] == 0
  394. assert event["parent_event_id"] is None
  395. assert event["parent_span_id"] is None
  396. assert len(event["errors"]) == 0
  397. assert current_transaction_event in events
  398. event = events[current_transaction_event]
  399. assert event["generation"] == 1
  400. assert event["parent_event_id"] == root_event_id
  401. assert event["parent_span_id"] == self.root_span_ids[0]
  402. assert len(event["errors"]) == 1
  403. assert event["errors"][0]["event_id"] == error.event_id
  404. assert event["errors"][0]["issue_id"] == error.group_id
  405. with self.feature(self.FEATURES):
  406. response = self.client.get(
  407. self.url,
  408. data={"event_id": error.event_id, "project": -1},
  409. format="json",
  410. )
  411. assertions(response)
  412. with self.feature(self.FEATURES):
  413. response = self.client.get(
  414. self.url,
  415. data={"event_id": current_transaction_event, "project": -1},
  416. format="json",
  417. )
  418. assertions(response)
  419. class OrganizationEventsTraceEndpointTest(OrganizationEventsTraceEndpointBase):
  420. url_name = "sentry-api-0-organization-events-trace"
  421. def assert_event(self, result, event_data, message):
  422. assert result["event_id"] == event_data.event_id, message
  423. assert result["timestamp"] == event_data.data["timestamp"], message
  424. assert result["start_timestamp"] == event_data.data["start_timestamp"], message
  425. def assert_trace_data(self, root, gen2_no_children=True):
  426. """ see the setUp docstring for an idea of what the response structure looks like """
  427. self.assert_event(root, self.root_event, "root")
  428. assert root["parent_event_id"] is None
  429. assert root["parent_span_id"] is None
  430. assert root["generation"] == 0
  431. assert root["transaction.duration"] == 3000
  432. assert len(root["children"]) == 3
  433. for i, gen1 in enumerate(root["children"]):
  434. self.assert_event(gen1, self.gen1_events[i], f"gen1_{i}")
  435. assert gen1["parent_event_id"] == self.root_event.event_id
  436. assert gen1["parent_span_id"] == self.root_span_ids[i]
  437. assert gen1["generation"] == 1
  438. assert gen1["transaction.duration"] == 2000
  439. assert len(gen1["children"]) == 1
  440. gen2 = gen1["children"][0]
  441. self.assert_event(gen2, self.gen2_events[i], f"gen2_{i}")
  442. assert gen2["parent_event_id"] == self.gen1_events[i].event_id
  443. assert gen2["parent_span_id"] == self.gen1_span_ids[i]
  444. assert gen2["generation"] == 2
  445. assert gen2["transaction.duration"] == 1000
  446. # Only the first gen2 descendent has a child
  447. if i == 0:
  448. assert len(gen2["children"]) == 1
  449. gen3 = gen2["children"][0]
  450. self.assert_event(gen3, self.gen3_event, f"gen3_{i}")
  451. assert gen3["parent_event_id"] == self.gen2_events[i].event_id
  452. assert gen3["parent_span_id"] == self.gen2_span_id
  453. assert gen3["generation"] == 3
  454. assert gen3["transaction.duration"] == 500
  455. assert len(gen3["children"]) == 0
  456. elif gen2_no_children:
  457. assert len(gen2["children"]) == 0
  458. def test_no_projects(self):
  459. user = self.create_user()
  460. org = self.create_organization(owner=user)
  461. self.login_as(user=user)
  462. url = reverse(
  463. self.url_name,
  464. kwargs={"organization_slug": org.slug, "trace_id": uuid4().hex},
  465. )
  466. with self.feature(self.FEATURES):
  467. response = self.client.get(
  468. url,
  469. format="json",
  470. )
  471. assert response.status_code == 404, response.content
  472. def test_simple(self):
  473. with self.feature(self.FEATURES):
  474. response = self.client.get(
  475. self.url,
  476. data={"project": -1},
  477. format="json",
  478. )
  479. assert response.status_code == 200, response.content
  480. self.assert_trace_data(response.data[0])
  481. # We shouldn't have detailed fields here
  482. assert "transaction.status" not in response.data[0]
  483. assert "tags" not in response.data[0]
  484. assert "measurements" not in response.data[0]
  485. def test_detailed_trace(self):
  486. with self.feature(self.FEATURES):
  487. response = self.client.get(
  488. self.url,
  489. data={"project": -1, "detailed": 1},
  490. format="json",
  491. )
  492. assert response.status_code == 200, response.content
  493. self.assert_trace_data(response.data[0])
  494. root = response.data[0]
  495. assert root["transaction.status"] == "ok"
  496. root_tags = {tag["key"]: tag["value"] for tag in root["tags"]}
  497. for [key, value] in self.root_event.tags:
  498. if not key.startswith("sentry:"):
  499. assert root_tags[key] == value, f"tags - {key}"
  500. else:
  501. assert root_tags[key[7:]] == value, f"tags - {key}"
  502. assert root["measurements"]["lcp"]["value"] == 1000
  503. assert root["measurements"]["fcp"]["value"] == 750
  504. def test_detailed_trace_with_bad_tags(self):
  505. """ Basically test that we're actually using the event serializer's method for tags """
  506. trace = uuid4().hex
  507. self.create_event(
  508. trace=trace,
  509. transaction="bad-tags",
  510. parent_span_id=None,
  511. spans=[],
  512. project_id=self.project.id,
  513. tags=[["somethinglong" * 250, "somethinglong" * 250]],
  514. duration=3000,
  515. assert_no_errors=False,
  516. )
  517. url = reverse(
  518. self.url_name,
  519. kwargs={"organization_slug": self.project.organization.slug, "trace_id": trace},
  520. )
  521. with self.feature(self.FEATURES):
  522. response = self.client.get(
  523. url,
  524. data={"project": -1, "detailed": 1},
  525. format="json",
  526. )
  527. assert response.status_code == 200, response.content
  528. root = response.data[0]
  529. assert root["transaction.status"] == "ok"
  530. assert {"key": None, "value": None} in root["tags"]
  531. def test_bad_span_loop(self):
  532. """Maliciously create a loop in the span structure
  533. Structure then becomes something like this:
  534. root
  535. gen1-0...
  536. gen1-1
  537. gen2-1
  538. gen3-1
  539. gen_2-1
  540. gen3-1...
  541. """
  542. gen3_loop_event = self.create_event(
  543. trace=self.trace_id,
  544. transaction="/transaction/gen3-1/loop",
  545. spans=[
  546. {
  547. "same_process_as_parent": True,
  548. "op": "http",
  549. "description": "GET gen2-1",
  550. "span_id": self.gen1_span_ids[1],
  551. "trace_id": self.trace_id,
  552. }
  553. ],
  554. parent_span_id=self.gen2_span_ids[1],
  555. project_id=self.project.id,
  556. )
  557. with self.feature(self.FEATURES):
  558. response = self.client.get(
  559. self.url,
  560. data={"project": -1},
  561. format="json",
  562. )
  563. assert response.status_code == 200, response.content
  564. # Should be the same as the simple testcase
  565. self.assert_trace_data(response.data[0], gen2_no_children=False)
  566. # The difference is that gen3-1 should exist with no children
  567. gen2_1 = response.data[0]["children"][1]["children"][0]
  568. assert len(gen2_1["children"]) == 1
  569. gen3_1 = gen2_1["children"][0]
  570. assert gen3_1["event_id"] == gen3_loop_event.event_id
  571. # We didn't even try to start the loop of spans
  572. assert len(gen3_1["children"]) == 0
  573. def test_sibling_transactions(self):
  574. """ More than one transaction can share a parent_span_id """
  575. gen3_event_siblings = [
  576. self.create_event(
  577. trace=self.trace_id,
  578. transaction="/transaction/gen3-1",
  579. spans=[],
  580. project_id=self.create_project(organization=self.organization).id,
  581. parent_span_id=self.gen2_span_ids[1],
  582. duration=525,
  583. ).event_id,
  584. self.create_event(
  585. trace=self.trace_id,
  586. transaction="/transaction/gen3-2",
  587. spans=[],
  588. project_id=self.create_project(organization=self.organization).id,
  589. parent_span_id=self.gen2_span_ids[1],
  590. duration=500,
  591. ).event_id,
  592. ]
  593. with self.feature(self.FEATURES):
  594. response = self.client.get(
  595. self.url,
  596. data={"project": -1},
  597. format="json",
  598. )
  599. assert response.status_code == 200, response.content
  600. # Should be the same as the simple testcase, but skip checking gen2 children
  601. self.assert_trace_data(response.data[0], gen2_no_children=False)
  602. gen2_parent = response.data[0]["children"][1]["children"][0]
  603. assert len(gen2_parent["children"]) == 2
  604. assert [child["event_id"] for child in gen2_parent["children"]] == gen3_event_siblings
  605. def test_with_orphan_siblings(self):
  606. parent_span_id = uuid4().hex[:16]
  607. root_event = self.create_event(
  608. trace=self.trace_id,
  609. transaction="/orphan/root",
  610. spans=[],
  611. # Some random id so its separated from the rest of the trace
  612. parent_span_id=parent_span_id,
  613. project_id=self.project.id,
  614. # Longer duration means that this event happened first, and should be ordered first
  615. duration=1250,
  616. )
  617. root_sibling_event = self.create_event(
  618. trace=self.trace_id,
  619. transaction="/orphan/root-sibling",
  620. spans=[],
  621. parent_span_id=parent_span_id,
  622. project_id=self.project.id,
  623. duration=1000,
  624. )
  625. with self.feature(self.FEATURES):
  626. response = self.client.get(
  627. self.url,
  628. data={"project": -1},
  629. format="json",
  630. )
  631. assert response.status_code == 200, response.content
  632. assert len(response.data) == 3
  633. # The first item of the response should be the main trace
  634. main, *orphans = response.data
  635. self.assert_trace_data(main)
  636. assert [root_event.event_id, root_sibling_event.event_id] == [
  637. orphan["event_id"] for orphan in orphans
  638. ]
  639. def test_with_orphan_trace(self):
  640. orphan_span_ids = {
  641. key: uuid4().hex[:16]
  642. for key in ["root", "root_span", "child", "child_span", "grandchild", "grandchild_span"]
  643. }
  644. # Create the orphan transactions
  645. root_event = self.create_event(
  646. trace=self.trace_id,
  647. transaction="/orphan/root",
  648. spans=[
  649. {
  650. "same_process_as_parent": True,
  651. "op": "http",
  652. "description": "GET gen1 orphan",
  653. "span_id": orphan_span_ids["root_span"],
  654. "trace_id": self.trace_id,
  655. }
  656. ],
  657. # Some random id so its separated from the rest of the trace
  658. parent_span_id=uuid4().hex[:16],
  659. span_id=orphan_span_ids["root"],
  660. project_id=self.project.id,
  661. duration=1000,
  662. )
  663. child_event = self.create_event(
  664. trace=self.trace_id,
  665. transaction="/orphan/child1-0",
  666. spans=[
  667. {
  668. "same_process_as_parent": True,
  669. "op": "http",
  670. "description": "GET gen1 orphan",
  671. "span_id": orphan_span_ids["child_span"],
  672. "trace_id": self.trace_id,
  673. }
  674. ],
  675. # Some random id so its separated from the rest of the trace
  676. parent_span_id=orphan_span_ids["root_span"],
  677. span_id=orphan_span_ids["child"],
  678. project_id=self.gen1_project.id,
  679. duration=500,
  680. )
  681. grandchild_event = self.create_event(
  682. trace=self.trace_id,
  683. transaction="/orphan/grandchild1-0",
  684. spans=[
  685. {
  686. "same_process_as_parent": True,
  687. "op": "http",
  688. "description": "GET gen1 orphan",
  689. "span_id": orphan_span_ids["grandchild_span"],
  690. "trace_id": self.trace_id,
  691. }
  692. ],
  693. # Some random id so its separated from the rest of the trace
  694. parent_span_id=orphan_span_ids["child_span"],
  695. span_id=orphan_span_ids["grandchild"],
  696. project_id=self.gen1_project.id,
  697. duration=1500,
  698. )
  699. with self.feature(self.FEATURES):
  700. response = self.client.get(
  701. self.url,
  702. data={"project": -1},
  703. format="json",
  704. )
  705. assert response.status_code == 200, response.content
  706. assert len(response.data) == 2
  707. # The first item of the response should be the main trace
  708. main, orphans = response.data
  709. self.assert_trace_data(main)
  710. self.assert_event(orphans, root_event, "orphan-root")
  711. assert len(orphans["children"]) == 1
  712. assert orphans["generation"] == 0
  713. child = orphans["children"][0]
  714. self.assert_event(child, child_event, "orphan-child")
  715. assert len(child["children"]) == 1
  716. assert child["generation"] == 1
  717. grandchild = child["children"][0]
  718. self.assert_event(grandchild, grandchild_event, "orphan-grandchild")
  719. assert grandchild["generation"] == 2
  720. def test_with_errors(self):
  721. start, _ = self.get_start_end(1000)
  722. error_data = load_data(
  723. "javascript",
  724. timestamp=start,
  725. )
  726. error_data["contexts"]["trace"] = {
  727. "type": "trace",
  728. "trace_id": self.trace_id,
  729. "span_id": self.gen1_span_ids[0],
  730. }
  731. error = self.store_event(error_data, project_id=self.gen1_project.id)
  732. error1 = self.store_event(error_data, project_id=self.gen1_project.id)
  733. with self.feature(self.FEATURES):
  734. response = self.client.get(
  735. self.url,
  736. data={"project": -1},
  737. format="json",
  738. )
  739. assert response.status_code == 200, response.content
  740. self.assert_trace_data(response.data[0])
  741. gen1_event = response.data[0]["children"][0]
  742. assert len(gen1_event["errors"]) == 2
  743. assert {
  744. "event_id": error.event_id,
  745. "issue_id": error.group_id,
  746. "span": self.gen1_span_ids[0],
  747. "project_id": self.gen1_project.id,
  748. "project_slug": self.gen1_project.slug,
  749. } in gen1_event["errors"]
  750. assert {
  751. "event_id": error1.event_id,
  752. "issue_id": error1.group_id,
  753. "span": self.gen1_span_ids[0],
  754. "project_id": self.gen1_project.id,
  755. "project_slug": self.gen1_project.slug,
  756. } in gen1_event["errors"]
  757. def test_with_default(self):
  758. start, _ = self.get_start_end(1000)
  759. default_event = self.store_event(
  760. {
  761. "timestamp": iso_format(start),
  762. "contexts": {
  763. "trace": {
  764. "type": "trace",
  765. "trace_id": self.trace_id,
  766. "span_id": self.root_span_ids[0],
  767. },
  768. },
  769. },
  770. project_id=self.gen1_project.id,
  771. )
  772. with self.feature(self.FEATURES):
  773. response = self.client.get(
  774. self.url,
  775. data={"project": -1},
  776. format="json",
  777. )
  778. assert response.status_code == 200, response.content
  779. self.assert_trace_data(response.data[0])
  780. root_event = response.data[0]
  781. assert len(root_event["errors"]) == 1
  782. assert {
  783. "event_id": default_event.event_id,
  784. "issue_id": default_event.group_id,
  785. "span": self.root_span_ids[0],
  786. "project_id": self.gen1_project.id,
  787. "project_slug": self.gen1_project.slug,
  788. } in root_event["errors"]