spanDescendantGroupBar.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import countBy from 'lodash/countBy';
  2. import {ROW_HEIGHT} from 'sentry/components/performance/waterfall/constants';
  3. import {DurationPill, RowRectangle} from 'sentry/components/performance/waterfall/rowBar';
  4. import {
  5. ConnectorBar,
  6. TOGGLE_BORDER_BOX,
  7. TreeConnector,
  8. } from 'sentry/components/performance/waterfall/treeConnector';
  9. import {
  10. getDurationDisplay,
  11. getHumanDuration,
  12. toPercent,
  13. } from 'sentry/components/performance/waterfall/utils';
  14. import {t} from 'sentry/locale';
  15. import {EventTransaction} from 'sentry/types/event';
  16. import theme from 'sentry/utils/theme';
  17. import {SpanGroupBar} from './spanGroupBar';
  18. import {EnhancedSpan, ProcessedSpanType, TreeDepthType} from './types';
  19. import {
  20. getSpanGroupBounds,
  21. getSpanGroupTimestamps,
  22. getSpanOperation,
  23. isOrphanSpan,
  24. isOrphanTreeDepth,
  25. SpanBoundsType,
  26. SpanGeneratedBoundsType,
  27. unwrapTreeDepth,
  28. } from './utils';
  29. type Props = {
  30. continuingTreeDepths: Array<TreeDepthType>;
  31. event: Readonly<EventTransaction>;
  32. generateBounds: (bounds: SpanBoundsType) => SpanGeneratedBoundsType;
  33. generateContentSpanBarRef: () => (instance: HTMLDivElement | null) => void;
  34. onWheel: (deltaX: number) => void;
  35. span: Readonly<ProcessedSpanType>;
  36. spanGrouping: EnhancedSpan[];
  37. spanNumber: number;
  38. toggleSpanGroup: () => void;
  39. treeDepth: number;
  40. };
  41. export function SpanDescendantGroupBar(props: Props) {
  42. const {
  43. continuingTreeDepths,
  44. event,
  45. generateBounds,
  46. span,
  47. spanGrouping,
  48. spanNumber,
  49. toggleSpanGroup,
  50. onWheel,
  51. generateContentSpanBarRef,
  52. } = props;
  53. function renderGroupSpansTitle() {
  54. if (spanGrouping.length === 0) {
  55. return '';
  56. }
  57. const operationCounts = countBy(spanGrouping, enhancedSpan =>
  58. getSpanOperation(enhancedSpan.span)
  59. );
  60. const hasOthers = Object.keys(operationCounts).length > 1;
  61. const [mostFrequentOperationName] = Object.entries(operationCounts).reduce(
  62. (acc, [operationNameKey, count]) => {
  63. if (count > acc[1]) {
  64. return [operationNameKey, count];
  65. }
  66. return acc;
  67. }
  68. );
  69. return (
  70. <strong>{`${t('Autogrouped ')}\u2014 ${mostFrequentOperationName}${
  71. hasOthers ? t(' and more') : ''
  72. }`}</strong>
  73. );
  74. }
  75. function renderSpanTreeConnector() {
  76. const {treeDepth: spanTreeDepth} = props;
  77. const connectorBars: Array<React.ReactNode> = continuingTreeDepths.map(treeDepth => {
  78. const depth: number = unwrapTreeDepth(treeDepth);
  79. if (depth === 0) {
  80. // do not render a connector bar at depth 0,
  81. // if we did render a connector bar, this bar would be placed at depth -1
  82. // which does not exist.
  83. return null;
  84. }
  85. const left = ((spanTreeDepth - depth) * (TOGGLE_BORDER_BOX / 2) + 2) * -1;
  86. return (
  87. <ConnectorBar
  88. style={{left}}
  89. key={`span-group-${depth}`}
  90. orphanBranch={isOrphanTreeDepth(treeDepth)}
  91. />
  92. );
  93. });
  94. connectorBars.push(
  95. <ConnectorBar
  96. style={{
  97. right: '15px',
  98. height: `${ROW_HEIGHT / 2}px`,
  99. bottom: `-${ROW_HEIGHT / 2 + 1}px`,
  100. top: 'auto',
  101. }}
  102. key="collapsed-span-group-row-bottom"
  103. orphanBranch={false}
  104. />
  105. );
  106. return (
  107. <TreeConnector isLast hasToggler orphanBranch={isOrphanSpan(span)}>
  108. {connectorBars}
  109. </TreeConnector>
  110. );
  111. }
  112. function renderSpanRectangles() {
  113. const bounds = getSpanGroupBounds(spanGrouping, generateBounds);
  114. const durationDisplay = getDurationDisplay(bounds);
  115. const {startTimestamp, endTimestamp} = getSpanGroupTimestamps(spanGrouping);
  116. const duration = Math.abs(endTimestamp - startTimestamp);
  117. const durationString = getHumanDuration(duration);
  118. return (
  119. <RowRectangle
  120. spanBarHatch={false}
  121. style={{
  122. backgroundColor: theme.blue300,
  123. left: `min(${toPercent(bounds.left || 0)}, calc(100% - 1px))`,
  124. width: toPercent(bounds.width || 0),
  125. }}
  126. >
  127. <DurationPill
  128. durationDisplay={durationDisplay}
  129. showDetail={false}
  130. spanBarHatch={false}
  131. >
  132. {durationString}
  133. </DurationPill>
  134. </RowRectangle>
  135. );
  136. }
  137. return (
  138. <SpanGroupBar
  139. data-test-id="span-descendant-group-bar"
  140. event={event}
  141. span={span}
  142. spanGrouping={spanGrouping}
  143. treeDepth={props.treeDepth}
  144. spanNumber={spanNumber}
  145. generateBounds={generateBounds}
  146. toggleSpanGroup={toggleSpanGroup}
  147. renderSpanTreeConnector={renderSpanTreeConnector}
  148. renderGroupSpansTitle={renderGroupSpansTitle}
  149. renderSpanRectangles={renderSpanRectangles}
  150. onWheel={onWheel}
  151. generateContentSpanBarRef={generateContentSpanBarRef}
  152. />
  153. );
  154. }