spanDescendantGroupBar.tsx 5.5 KB

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