traceDrawer.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import {useCallback, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {t} from 'sentry/locale';
  5. import {space} from 'sentry/styles/space';
  6. import type {EventTransaction, Organization} from 'sentry/types';
  7. import type EventView from 'sentry/utils/discover/eventView';
  8. import type {
  9. TraceFullDetailed,
  10. TraceSplitResults,
  11. } from 'sentry/utils/performance/quickTrace/types';
  12. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  13. import type RequestError from 'sentry/utils/requestError/requestError';
  14. import {useResizableDrawer} from 'sentry/utils/useResizableDrawer';
  15. import type {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/virtualizedViewManager';
  16. import {
  17. isAutogroupedNode,
  18. isMissingInstrumentationNode,
  19. isSpanNode,
  20. isTraceErrorNode,
  21. isTransactionNode,
  22. } from '../guards';
  23. import type {TraceTree, TraceTreeNode} from '../traceTree';
  24. import NodeDetail from './tabs/details';
  25. import {TraceLevelDetails} from './tabs/trace';
  26. const MIN_PANEL_HEIGHT = 31;
  27. const DEFAULT_PANEL_HEIGHT = 200;
  28. function getTabTitle(node: TraceTreeNode<TraceTree.NodeValue>) {
  29. if (isTransactionNode(node)) {
  30. return (
  31. t('Transaction: ') +
  32. node.value['transaction.op'] +
  33. (node.value.transaction ? ' - ' + node.value.transaction : '')
  34. );
  35. }
  36. if (isSpanNode(node)) {
  37. return (
  38. t('Span: ') +
  39. node.value.op +
  40. (node.value.description ? ' - ' + node.value.description : '')
  41. );
  42. }
  43. if (isAutogroupedNode(node)) {
  44. return t('Autogroup');
  45. }
  46. if (isMissingInstrumentationNode(node)) {
  47. return t('Missing Instrumentation');
  48. }
  49. if (isTraceErrorNode(node)) {
  50. return node.value.title || 'Error';
  51. }
  52. return t('Detail');
  53. }
  54. type TraceDrawerProps = {
  55. activeTab: 'trace' | 'node';
  56. location: Location;
  57. manager: VirtualizedViewManager;
  58. nodes: TraceTreeNode<TraceTree.NodeValue>[];
  59. organization: Organization;
  60. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  61. scrollToNode: (node: TraceTreeNode<TraceTree.NodeValue>) => void;
  62. setActiveTab: (tab: 'trace' | 'node') => void;
  63. trace: TraceTree;
  64. traceEventView: EventView;
  65. traces: TraceSplitResults<TraceFullDetailed> | null;
  66. };
  67. function TraceDrawer(props: TraceDrawerProps) {
  68. const panelRef = useRef<HTMLDivElement>(null);
  69. const onResize = useCallback((newSize: number, maybeOldSize: number | undefined) => {
  70. if (!panelRef.current) {
  71. return;
  72. }
  73. panelRef.current.style.height = `${maybeOldSize ?? newSize}px`;
  74. panelRef.current.style.width = `100%`;
  75. }, []);
  76. const {onMouseDown} = useResizableDrawer({
  77. direction: 'up',
  78. initialSize: DEFAULT_PANEL_HEIGHT,
  79. min: MIN_PANEL_HEIGHT,
  80. sizeStorageKey: 'trace-drawer',
  81. onResize,
  82. });
  83. return (
  84. <PanelWrapper ref={panelRef}>
  85. <ResizeableHandle onMouseDown={onMouseDown} />
  86. <TabsContainer>
  87. <Tab
  88. active={props.activeTab === 'trace'}
  89. onClick={() => props.setActiveTab('trace')}
  90. >
  91. <TabButton>{t('Trace')}</TabButton>
  92. </Tab>
  93. {props.nodes.map((node, index) => {
  94. const title = getTabTitle(node);
  95. return (
  96. <Tab
  97. key={index}
  98. active={props.activeTab === 'node'}
  99. onClick={() => props.setActiveTab('node')}
  100. >
  101. <TabButton title={title}>{title}</TabButton>
  102. </Tab>
  103. );
  104. })}
  105. </TabsContainer>
  106. <Content>
  107. {props.activeTab === 'trace' ? (
  108. <TraceLevelDetails
  109. tree={props.trace}
  110. rootEventResults={props.rootEventResults}
  111. organization={props.organization}
  112. location={props.location}
  113. traces={props.traces}
  114. traceEventView={props.traceEventView}
  115. />
  116. ) : (
  117. props.nodes.map((node, index) => (
  118. <NodeDetail
  119. key={index}
  120. node={node}
  121. organization={props.organization}
  122. location={props.location}
  123. manager={props.manager}
  124. scrollToNode={props.scrollToNode}
  125. />
  126. ))
  127. )}
  128. </Content>
  129. </PanelWrapper>
  130. );
  131. }
  132. const ResizeableHandle = styled('div')`
  133. width: 100%;
  134. height: 12px;
  135. cursor: ns-resize;
  136. position: absolute;
  137. top: -6px;
  138. left: 0;
  139. z-index: 1;
  140. `;
  141. const PanelWrapper = styled('div')`
  142. grid-area: drawer;
  143. display: flex;
  144. flex-direction: column;
  145. width: 100%;
  146. position: sticky;
  147. border-top: 1px solid ${p => p.theme.border};
  148. bottom: 0;
  149. right: 0;
  150. background: ${p => p.theme.background};
  151. color: ${p => p.theme.textColor};
  152. text-align: left;
  153. z-index: 10;
  154. `;
  155. const TabsContainer = styled('ul')`
  156. list-style-type: none;
  157. width: 100%;
  158. min-height: 30px;
  159. border-bottom: 1px solid ${p => p.theme.border};
  160. background-color: ${p => p.theme.backgroundSecondary};
  161. display: flex;
  162. align-items: center;
  163. justify-content: left;
  164. padding-left: ${space(2)};
  165. gap: ${space(1)};
  166. margin-bottom: 0;
  167. `;
  168. const Tab = styled('li')<{active: boolean}>`
  169. height: 100%;
  170. button {
  171. border-bottom: 2px solid ${p => (p.active ? p.theme.blue400 : 'transparent')};
  172. font-weight: ${p => (p.active ? 'bold' : 'normal')};
  173. }
  174. `;
  175. const TabButton = styled('button')`
  176. height: 100%;
  177. border: none;
  178. max-width: 260px;
  179. overflow: hidden;
  180. text-overflow: ellipsis;
  181. white-space: nowrap;
  182. border-top: 2px solid transparent;
  183. border-bottom: 2px solid transparent;
  184. border-radius: 0;
  185. margin: 0;
  186. padding: ${space(0.25)};
  187. font-size: ${p => p.theme.fontSizeSmall};
  188. color: ${p => p.theme.textColor};
  189. background: transparent;
  190. `;
  191. const Content = styled('div')`
  192. overflow: scroll;
  193. padding: ${space(1)};
  194. flex: 1;
  195. `;
  196. export default TraceDrawer;