traceDrawer.tsx 5.5 KB

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