spanTreeModel.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. import {action, computed, makeObservable, observable} from 'mobx';
  2. import {Client} from 'sentry/api';
  3. import {t} from 'sentry/locale';
  4. import {EventTransaction} from 'sentry/types/event';
  5. import {ActiveOperationFilter} from './filter';
  6. import {
  7. EnhancedProcessedSpanType,
  8. EnhancedSpan,
  9. FetchEmbeddedChildrenState,
  10. FilterSpans,
  11. OrphanTreeDepth,
  12. RawSpanType,
  13. SpanChildrenLookupType,
  14. SpanType,
  15. TraceBound,
  16. TreeDepthType,
  17. } from './types';
  18. import {
  19. generateRootSpan,
  20. getSiblingGroupKey,
  21. getSpanID,
  22. getSpanOperation,
  23. isEventFromBrowserJavaScriptSDK,
  24. isOrphanSpan,
  25. parseTrace,
  26. SpanBoundsType,
  27. SpanGeneratedBoundsType,
  28. } from './utils';
  29. const MIN_SIBLING_GROUP_SIZE = 5;
  30. class SpanTreeModel {
  31. api: Client;
  32. // readonly state
  33. span: Readonly<SpanType>;
  34. children: Array<SpanTreeModel> = [];
  35. isRoot: boolean;
  36. // readable/writable state
  37. fetchEmbeddedChildrenState: FetchEmbeddedChildrenState = 'idle';
  38. showEmbeddedChildren: boolean = false;
  39. embeddedChildren: Array<SpanTreeModel> = [];
  40. // This controls if a chain of nested spans that are the only sibling to be visually grouped together or not.
  41. // On initial render, they're visually grouped together.
  42. isNestedSpanGroupExpanded: boolean = false;
  43. // Entries in this set will follow the format 'op.description'.
  44. // An entry in this set indicates that all siblings with the op and description should be left ungrouped
  45. expandedSiblingGroups: Set<string> = new Set();
  46. constructor(
  47. parentSpan: SpanType,
  48. childSpans: SpanChildrenLookupType,
  49. api: Client,
  50. isRoot: boolean = false
  51. ) {
  52. this.api = api;
  53. this.span = parentSpan;
  54. this.isRoot = isRoot;
  55. const spanID = getSpanID(parentSpan);
  56. const spanChildren: Array<RawSpanType> = childSpans?.[spanID] ?? [];
  57. // Mark descendents as being rendered. This is to address potential recursion issues due to malformed data.
  58. // For example if a span has a span_id that's identical to its parent_span_id.
  59. childSpans = {
  60. ...childSpans,
  61. };
  62. delete childSpans[spanID];
  63. this.children = spanChildren.map(span => {
  64. return new SpanTreeModel(span, childSpans, api);
  65. });
  66. makeObservable(this, {
  67. operationNameCounts: computed.struct,
  68. showEmbeddedChildren: observable,
  69. embeddedChildren: observable,
  70. fetchEmbeddedChildrenState: observable,
  71. toggleEmbeddedChildren: action,
  72. fetchEmbeddedTransactions: action,
  73. isNestedSpanGroupExpanded: observable,
  74. toggleNestedSpanGroup: action,
  75. expandedSiblingGroups: observable,
  76. toggleSiblingSpanGroup: action,
  77. });
  78. }
  79. get operationNameCounts(): Map<string, number> {
  80. const result = new Map<string, number>();
  81. const operationName = this.span.op;
  82. if (typeof operationName === 'string' && operationName.length > 0) {
  83. result.set(operationName, 1);
  84. }
  85. for (const directChild of this.children) {
  86. const operationNameCounts = directChild.operationNameCounts;
  87. for (const [key, count] of operationNameCounts) {
  88. result.set(key, (result.get(key) ?? 0) + count);
  89. }
  90. }
  91. // sort alphabetically using case insensitive comparison
  92. return new Map(
  93. [...result].sort((a, b) =>
  94. String(a[0]).localeCompare(b[0], undefined, {sensitivity: 'base'})
  95. )
  96. );
  97. }
  98. isSpanFilteredOut = (
  99. props: {
  100. filterSpans: FilterSpans | undefined;
  101. operationNameFilters: ActiveOperationFilter;
  102. },
  103. spanModel: SpanTreeModel
  104. ): boolean => {
  105. const {operationNameFilters, filterSpans} = props;
  106. if (operationNameFilters.type === 'active_filter') {
  107. const operationName = getSpanOperation(spanModel.span);
  108. if (
  109. typeof operationName === 'string' &&
  110. !operationNameFilters.operationNames.has(operationName)
  111. ) {
  112. return true;
  113. }
  114. }
  115. if (!filterSpans) {
  116. return false;
  117. }
  118. return !filterSpans.spanIDs.has(getSpanID(spanModel.span));
  119. };
  120. generateSpanGap(
  121. event: Readonly<EventTransaction>,
  122. previousSiblingEndTimestamp: number | undefined,
  123. treeDepth: number,
  124. continuingTreeDepths: Array<TreeDepthType>
  125. ): EnhancedProcessedSpanType | undefined {
  126. // hide gap spans (i.e. "missing instrumentation" spans) for browser js transactions,
  127. // since they're not useful to indicate
  128. const shouldIncludeGap = !isEventFromBrowserJavaScriptSDK(event);
  129. const isValidGap =
  130. shouldIncludeGap &&
  131. typeof previousSiblingEndTimestamp === 'number' &&
  132. previousSiblingEndTimestamp < this.span.start_timestamp &&
  133. // gap is at least 100 ms
  134. this.span.start_timestamp - previousSiblingEndTimestamp >= 0.1;
  135. if (!isValidGap) {
  136. return undefined;
  137. }
  138. const gapSpan: EnhancedProcessedSpanType = {
  139. type: 'gap',
  140. span: {
  141. type: 'gap',
  142. start_timestamp: previousSiblingEndTimestamp || this.span.start_timestamp,
  143. timestamp: this.span.start_timestamp, // this is essentially end_timestamp
  144. description: t('Missing instrumentation'),
  145. isOrphan: isOrphanSpan(this.span),
  146. },
  147. numOfSpanChildren: 0,
  148. treeDepth,
  149. isLastSibling: false,
  150. continuingTreeDepths,
  151. fetchEmbeddedChildrenState: 'idle',
  152. showEmbeddedChildren: false,
  153. toggleEmbeddedChildren: undefined,
  154. };
  155. return gapSpan;
  156. }
  157. getSpansList = (props: {
  158. addTraceBounds: (bounds: TraceBound) => void;
  159. continuingTreeDepths: Array<TreeDepthType>;
  160. event: Readonly<EventTransaction>;
  161. filterSpans: FilterSpans | undefined;
  162. generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
  163. hiddenSpanSubTrees: Set<String>;
  164. isAutogroupSiblingFeatureEnabled: boolean;
  165. isLastSibling: boolean;
  166. isNestedSpanGroupExpanded: boolean;
  167. isOnlySibling: boolean;
  168. operationNameFilters: ActiveOperationFilter;
  169. previousSiblingEndTimestamp: number | undefined;
  170. removeTraceBounds: (eventSlug: string) => void;
  171. spanAncestors: Set<String>;
  172. spanNestedGrouping: EnhancedSpan[] | undefined;
  173. toggleNestedSpanGroup: (() => void) | undefined;
  174. treeDepth: number;
  175. }): EnhancedProcessedSpanType[] => {
  176. const {
  177. operationNameFilters,
  178. generateBounds,
  179. isLastSibling,
  180. hiddenSpanSubTrees,
  181. // The set of ancestor span IDs whose sub-tree that the span belongs to
  182. spanAncestors,
  183. filterSpans,
  184. previousSiblingEndTimestamp,
  185. event,
  186. isOnlySibling,
  187. spanNestedGrouping,
  188. toggleNestedSpanGroup,
  189. isNestedSpanGroupExpanded,
  190. addTraceBounds,
  191. removeTraceBounds,
  192. isAutogroupSiblingFeatureEnabled,
  193. } = props;
  194. let {treeDepth, continuingTreeDepths} = props;
  195. const parentSpanID = getSpanID(this.span);
  196. const nextSpanAncestors = new Set(spanAncestors);
  197. nextSpanAncestors.add(parentSpanID);
  198. const descendantsSource = this.showEmbeddedChildren
  199. ? [...this.embeddedChildren, ...this.children]
  200. : this.children;
  201. const lastIndex = descendantsSource.length - 1;
  202. const isNotLastSpanOfGroup =
  203. isOnlySibling && !this.isRoot && descendantsSource.length === 1;
  204. const shouldGroup = isNotLastSpanOfGroup;
  205. const hideSpanTree = hiddenSpanSubTrees.has(parentSpanID);
  206. const isLastSpanOfGroup =
  207. isOnlySibling && !this.isRoot && (descendantsSource.length !== 1 || hideSpanTree);
  208. const isFirstSpanOfGroup =
  209. shouldGroup &&
  210. (spanNestedGrouping === undefined ||
  211. (Array.isArray(spanNestedGrouping) && spanNestedGrouping.length === 0));
  212. if (
  213. isLastSpanOfGroup &&
  214. Array.isArray(spanNestedGrouping) &&
  215. spanNestedGrouping.length >= 1 &&
  216. !isNestedSpanGroupExpanded
  217. ) {
  218. // We always want to indent the last span of the span group chain
  219. treeDepth = treeDepth + 1;
  220. // For a collapsed span group chain to be useful, we prefer span groupings
  221. // that are two or more spans.
  222. // Since there is no concept of "backtracking" when constructing the span tree,
  223. // we will need to reconstruct the tree depth information. This is only neccessary
  224. // when the span group chain is hidden/collapsed.
  225. if (spanNestedGrouping.length === 1) {
  226. const treeDepthEntry = isOrphanSpan(spanNestedGrouping[0].span)
  227. ? ({type: 'orphan', depth: spanNestedGrouping[0].treeDepth} as OrphanTreeDepth)
  228. : spanNestedGrouping[0].treeDepth;
  229. if (!spanNestedGrouping[0].isLastSibling) {
  230. continuingTreeDepths = [...continuingTreeDepths, treeDepthEntry];
  231. }
  232. }
  233. }
  234. // Criteria for propagating information about the span group to the last span of the span group chain
  235. const spanGroupingCriteria =
  236. isLastSpanOfGroup &&
  237. Array.isArray(spanNestedGrouping) &&
  238. spanNestedGrouping.length > 1;
  239. const wrappedSpan: EnhancedSpan = {
  240. type: this.isRoot ? 'root_span' : 'span',
  241. span: this.span,
  242. numOfSpanChildren: descendantsSource.length,
  243. treeDepth,
  244. isLastSibling,
  245. continuingTreeDepths,
  246. fetchEmbeddedChildrenState: this.fetchEmbeddedChildrenState,
  247. showEmbeddedChildren: this.showEmbeddedChildren,
  248. toggleEmbeddedChildren: this.toggleEmbeddedChildren({
  249. addTraceBounds,
  250. removeTraceBounds,
  251. }),
  252. toggleNestedSpanGroup:
  253. spanGroupingCriteria && toggleNestedSpanGroup && !isNestedSpanGroupExpanded
  254. ? toggleNestedSpanGroup
  255. : isFirstSpanOfGroup && this.isNestedSpanGroupExpanded && !hideSpanTree
  256. ? this.toggleNestedSpanGroup
  257. : undefined,
  258. toggleSiblingSpanGroup: undefined,
  259. };
  260. if (wrappedSpan.type === 'root_span') {
  261. // @ts-expect-error
  262. delete wrappedSpan.toggleNestedSpanGroup;
  263. }
  264. const treeDepthEntry = isOrphanSpan(this.span)
  265. ? ({type: 'orphan', depth: treeDepth} as OrphanTreeDepth)
  266. : treeDepth;
  267. const shouldHideSpanOfGroup =
  268. shouldGroup &&
  269. !isLastSpanOfGroup &&
  270. ((toggleNestedSpanGroup === undefined && !this.isNestedSpanGroupExpanded) ||
  271. (toggleNestedSpanGroup !== undefined && !isNestedSpanGroupExpanded));
  272. const descendantContinuingTreeDepths =
  273. isLastSibling || shouldHideSpanOfGroup
  274. ? continuingTreeDepths
  275. : [...continuingTreeDepths, treeDepthEntry];
  276. for (const hiddenSpanSubTree of hiddenSpanSubTrees) {
  277. if (spanAncestors.has(hiddenSpanSubTree)) {
  278. // If this span is hidden, then all the descendants are hidden as well
  279. return [];
  280. }
  281. }
  282. let descendants: EnhancedProcessedSpanType[];
  283. if (isAutogroupSiblingFeatureEnabled) {
  284. // Check if the descendants in this span have consecutive similar siblings, and group them
  285. const groupedDescendants: SpanTreeModel[][] = [];
  286. if (descendantsSource?.length >= MIN_SIBLING_GROUP_SIZE) {
  287. let prevSpanModel = descendantsSource[0];
  288. let currentGroup = [prevSpanModel];
  289. for (let i = 1; i < descendantsSource.length; i++) {
  290. const currSpanModel = descendantsSource[i];
  291. // We want to group siblings only if they share the same op and description, and if they have no children
  292. if (
  293. prevSpanModel.span.op === currSpanModel.span.op &&
  294. prevSpanModel.span.description === currSpanModel.span.description &&
  295. currSpanModel.children.length === 0
  296. ) {
  297. currentGroup.push(currSpanModel);
  298. } else {
  299. groupedDescendants.push(currentGroup);
  300. if (currSpanModel.children.length) {
  301. currentGroup = [currSpanModel];
  302. groupedDescendants.push(currentGroup);
  303. currentGroup = [];
  304. } else {
  305. currentGroup = [currSpanModel];
  306. }
  307. }
  308. prevSpanModel = currSpanModel;
  309. }
  310. groupedDescendants.push(currentGroup);
  311. } else if (descendantsSource.length >= 1) {
  312. groupedDescendants.push(descendantsSource);
  313. }
  314. descendants = (hideSpanTree ? [] : groupedDescendants).reduce(
  315. (
  316. acc: {
  317. descendants: EnhancedProcessedSpanType[];
  318. previousSiblingEndTimestamp: number | undefined;
  319. },
  320. group,
  321. groupIndex
  322. ) => {
  323. // Groups less than 5 indicate that the spans should be left ungrouped
  324. if (group.length < MIN_SIBLING_GROUP_SIZE) {
  325. group.forEach((spanModel, index) => {
  326. acc.descendants.push(
  327. ...spanModel.getSpansList({
  328. operationNameFilters,
  329. generateBounds,
  330. treeDepth: shouldHideSpanOfGroup ? treeDepth : treeDepth + 1,
  331. isLastSibling:
  332. groupIndex === groupedDescendants.length - 1 &&
  333. index === group.length - 1,
  334. continuingTreeDepths: descendantContinuingTreeDepths,
  335. hiddenSpanSubTrees,
  336. spanAncestors: new Set(nextSpanAncestors),
  337. filterSpans,
  338. previousSiblingEndTimestamp: acc.previousSiblingEndTimestamp,
  339. event,
  340. isOnlySibling: descendantsSource.length === 1,
  341. spanNestedGrouping: shouldGroup
  342. ? [...(spanNestedGrouping ?? []), wrappedSpan]
  343. : undefined,
  344. toggleNestedSpanGroup: isNotLastSpanOfGroup
  345. ? toggleNestedSpanGroup === undefined
  346. ? this.toggleNestedSpanGroup
  347. : toggleNestedSpanGroup
  348. : undefined,
  349. isNestedSpanGroupExpanded: isNotLastSpanOfGroup
  350. ? toggleNestedSpanGroup === undefined
  351. ? this.isNestedSpanGroupExpanded
  352. : isNestedSpanGroupExpanded
  353. : false,
  354. addTraceBounds,
  355. removeTraceBounds,
  356. isAutogroupSiblingFeatureEnabled,
  357. })
  358. );
  359. acc.previousSiblingEndTimestamp = spanModel.span.timestamp;
  360. });
  361. return acc;
  362. }
  363. // NOTE: I am making the assumption here that grouped sibling spans will not have children.
  364. // By making this assumption, I can immediately wrap the grouped spans here without having
  365. // to recursively traverse them.
  366. // This may not be the case, and needs to be looked into later
  367. const wrappedSiblings: EnhancedSpan[] = group.map((spanModel, index) => {
  368. const enhancedSibling: EnhancedSpan = {
  369. type: 'span',
  370. span: spanModel.span,
  371. numOfSpanChildren: 0,
  372. treeDepth: treeDepth + 1,
  373. isLastSibling:
  374. index === group.length - 1 &&
  375. groupIndex === groupedDescendants.length - 1,
  376. isFirstSiblingOfGroup: index === 0,
  377. continuingTreeDepths: descendantContinuingTreeDepths,
  378. fetchEmbeddedChildrenState: spanModel.fetchEmbeddedChildrenState,
  379. showEmbeddedChildren: spanModel.showEmbeddedChildren,
  380. toggleEmbeddedChildren: spanModel.toggleEmbeddedChildren({
  381. addTraceBounds,
  382. removeTraceBounds,
  383. }),
  384. toggleNestedSpanGroup: undefined,
  385. toggleSiblingSpanGroup:
  386. index === 0 ? this.toggleSiblingSpanGroup : undefined,
  387. };
  388. return enhancedSibling;
  389. });
  390. if (this.isSpanFilteredOut(props, group[0])) {
  391. group.forEach(spanModel =>
  392. acc.descendants.push({
  393. type: 'filtered_out',
  394. span: spanModel.span,
  395. })
  396. );
  397. return acc;
  398. }
  399. const groupedSiblingsSpan: EnhancedProcessedSpanType = {
  400. type: 'span_group_sibling',
  401. span: this.span,
  402. treeDepth: treeDepth + 1,
  403. continuingTreeDepths,
  404. spanSiblingGrouping: wrappedSiblings,
  405. isLastSibling: groupIndex === groupedDescendants.length - 1,
  406. toggleSiblingSpanGroup: this.toggleSiblingSpanGroup,
  407. };
  408. acc.previousSiblingEndTimestamp =
  409. wrappedSiblings[wrappedSiblings.length - 1].span.timestamp;
  410. // Check if the group is currently expanded or not
  411. const key = `${group[0].span.op}.${group[0].span.description}`;
  412. if (this.expandedSiblingGroups.has(key)) {
  413. acc.descendants.push(...wrappedSiblings);
  414. return acc;
  415. }
  416. acc.descendants.push(groupedSiblingsSpan);
  417. return acc;
  418. },
  419. {
  420. descendants: [],
  421. previousSiblingEndTimestamp: undefined,
  422. }
  423. ).descendants;
  424. } else {
  425. descendants = (hideSpanTree ? [] : descendantsSource).reduce(
  426. (
  427. acc: {
  428. descendants: EnhancedProcessedSpanType[];
  429. previousSiblingEndTimestamp: number | undefined;
  430. },
  431. span,
  432. index
  433. ) => {
  434. acc.descendants.push(
  435. ...span.getSpansList({
  436. operationNameFilters,
  437. generateBounds,
  438. treeDepth: shouldHideSpanOfGroup ? treeDepth : treeDepth + 1,
  439. isLastSibling: index === lastIndex,
  440. continuingTreeDepths: descendantContinuingTreeDepths,
  441. hiddenSpanSubTrees,
  442. spanAncestors: new Set(nextSpanAncestors),
  443. filterSpans,
  444. previousSiblingEndTimestamp: acc.previousSiblingEndTimestamp,
  445. event,
  446. isOnlySibling: descendantsSource.length === 1,
  447. spanNestedGrouping: shouldGroup
  448. ? [...(spanNestedGrouping ?? []), wrappedSpan]
  449. : undefined,
  450. toggleNestedSpanGroup: isNotLastSpanOfGroup
  451. ? toggleNestedSpanGroup === undefined
  452. ? this.toggleNestedSpanGroup
  453. : toggleNestedSpanGroup
  454. : undefined,
  455. isNestedSpanGroupExpanded: isNotLastSpanOfGroup
  456. ? toggleNestedSpanGroup === undefined
  457. ? this.isNestedSpanGroupExpanded
  458. : isNestedSpanGroupExpanded
  459. : false,
  460. addTraceBounds,
  461. removeTraceBounds,
  462. isAutogroupSiblingFeatureEnabled,
  463. })
  464. );
  465. acc.previousSiblingEndTimestamp = span.span.timestamp;
  466. return acc;
  467. },
  468. {
  469. descendants: [],
  470. previousSiblingEndTimestamp: undefined,
  471. }
  472. ).descendants;
  473. }
  474. if (this.isSpanFilteredOut(props, this)) {
  475. return [
  476. {
  477. type: 'filtered_out',
  478. span: this.span,
  479. },
  480. ...descendants,
  481. ];
  482. }
  483. const bounds = generateBounds({
  484. startTimestamp: this.span.start_timestamp,
  485. endTimestamp: this.span.timestamp,
  486. });
  487. const isCurrentSpanOutOfView = !bounds.isSpanVisibleInView;
  488. if (isCurrentSpanOutOfView) {
  489. return [
  490. {
  491. type: 'out_of_view',
  492. span: this.span,
  493. },
  494. ...descendants,
  495. ];
  496. }
  497. if (shouldHideSpanOfGroup) {
  498. return [...descendants];
  499. }
  500. if (
  501. isLastSpanOfGroup &&
  502. Array.isArray(spanNestedGrouping) &&
  503. spanNestedGrouping.length > 1 &&
  504. !isNestedSpanGroupExpanded &&
  505. wrappedSpan.type === 'span'
  506. ) {
  507. const spanGroupChain: EnhancedProcessedSpanType = {
  508. type: 'span_group_chain',
  509. span: this.span,
  510. treeDepth: treeDepth - 1,
  511. continuingTreeDepths,
  512. spanNestedGrouping,
  513. isNestedSpanGroupExpanded,
  514. toggleNestedSpanGroup: wrappedSpan.toggleNestedSpanGroup,
  515. toggleSiblingSpanGroup: undefined,
  516. };
  517. return [
  518. spanGroupChain,
  519. {...wrappedSpan, toggleNestedSpanGroup: undefined},
  520. ...descendants,
  521. ];
  522. }
  523. if (
  524. isFirstSpanOfGroup &&
  525. this.isNestedSpanGroupExpanded &&
  526. !hideSpanTree &&
  527. descendants.length <= 1 &&
  528. wrappedSpan.type === 'span'
  529. ) {
  530. // If we know the descendants will be one span or less, we remove the "regroup" feature (therefore hide it)
  531. // by setting toggleNestedSpanGroup to be undefined for the first span of the group chain.
  532. wrappedSpan.toggleNestedSpanGroup = undefined;
  533. }
  534. // Do not autogroup groups that will only have two spans
  535. if (
  536. isLastSpanOfGroup &&
  537. Array.isArray(spanNestedGrouping) &&
  538. spanNestedGrouping.length === 1
  539. ) {
  540. if (!isNestedSpanGroupExpanded) {
  541. const parentSpan = spanNestedGrouping[0].span;
  542. const parentSpanBounds = generateBounds({
  543. startTimestamp: parentSpan.start_timestamp,
  544. endTimestamp: parentSpan.timestamp,
  545. });
  546. const isParentSpanOutOfView = !parentSpanBounds.isSpanVisibleInView;
  547. if (!isParentSpanOutOfView) {
  548. return [spanNestedGrouping[0], wrappedSpan, ...descendants];
  549. }
  550. }
  551. return [wrappedSpan, ...descendants];
  552. }
  553. const gapSpan = this.generateSpanGap(
  554. event,
  555. previousSiblingEndTimestamp,
  556. treeDepth,
  557. continuingTreeDepths
  558. );
  559. if (gapSpan) {
  560. return [gapSpan, wrappedSpan, ...descendants];
  561. }
  562. return [wrappedSpan, ...descendants];
  563. };
  564. toggleEmbeddedChildren =
  565. ({
  566. addTraceBounds,
  567. removeTraceBounds,
  568. }: {
  569. addTraceBounds: (bounds: TraceBound) => void;
  570. removeTraceBounds: (eventSlug: string) => void;
  571. }) =>
  572. (props: {eventSlug: string; orgSlug: string}) => {
  573. this.showEmbeddedChildren = !this.showEmbeddedChildren;
  574. this.fetchEmbeddedChildrenState = 'idle';
  575. if (!this.showEmbeddedChildren) {
  576. if (this.embeddedChildren.length > 0) {
  577. this.embeddedChildren.forEach(child => {
  578. removeTraceBounds(child.generateTraceBounds().spanId);
  579. });
  580. }
  581. }
  582. if (this.showEmbeddedChildren) {
  583. if (this.embeddedChildren.length === 0) {
  584. return this.fetchEmbeddedTransactions({...props, addTraceBounds});
  585. }
  586. this.embeddedChildren.forEach(child => {
  587. addTraceBounds(child.generateTraceBounds());
  588. });
  589. }
  590. return Promise.resolve(undefined);
  591. };
  592. fetchEmbeddedTransactions({
  593. orgSlug,
  594. eventSlug,
  595. addTraceBounds,
  596. }: {
  597. addTraceBounds: (bounds: TraceBound) => void;
  598. eventSlug: string;
  599. orgSlug: string;
  600. }) {
  601. const url = `/organizations/${orgSlug}/events/${eventSlug}/`;
  602. this.fetchEmbeddedChildrenState = 'loading_embedded_transactions';
  603. return this.api
  604. .requestPromise(url, {
  605. method: 'GET',
  606. query: {},
  607. })
  608. .then(
  609. action('fetchEmbeddedTransactionsSuccess', (event: EventTransaction) => {
  610. if (!event) {
  611. return;
  612. }
  613. const parsedTrace = parseTrace(event);
  614. const rootSpan = generateRootSpan(parsedTrace);
  615. const parsedRootSpan = new SpanTreeModel(
  616. rootSpan,
  617. parsedTrace.childSpans,
  618. this.api,
  619. false
  620. );
  621. this.embeddedChildren = [parsedRootSpan];
  622. this.fetchEmbeddedChildrenState = 'idle';
  623. addTraceBounds(parsedRootSpan.generateTraceBounds());
  624. })
  625. )
  626. .catch(
  627. action('fetchEmbeddedTransactionsError', () => {
  628. this.embeddedChildren = [];
  629. this.fetchEmbeddedChildrenState = 'error_fetching_embedded_transactions';
  630. })
  631. );
  632. }
  633. toggleNestedSpanGroup = () => {
  634. this.isNestedSpanGroupExpanded = !this.isNestedSpanGroupExpanded;
  635. };
  636. toggleSiblingSpanGroup = (span: SpanType) => {
  637. const key = getSiblingGroupKey(span);
  638. if (this.expandedSiblingGroups.has(key)) {
  639. this.expandedSiblingGroups.delete(key);
  640. } else {
  641. this.expandedSiblingGroups.add(key);
  642. }
  643. };
  644. generateTraceBounds = (): TraceBound => {
  645. return {
  646. spanId: this.span.span_id,
  647. traceStartTimestamp: this.span.start_timestamp,
  648. traceEndTimestamp: this.span.timestamp,
  649. };
  650. };
  651. }
  652. export default SpanTreeModel;