123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import {useCallback, useRef} from 'react';
- import styled from '@emotion/styled';
- import type {Location} from 'history';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {EventTransaction, Organization} from 'sentry/types';
- import type EventView from 'sentry/utils/discover/eventView';
- import type {
- TraceFullDetailed,
- TraceSplitResults,
- } from 'sentry/utils/performance/quickTrace/types';
- import type {UseApiQueryResult} from 'sentry/utils/queryClient';
- import type RequestError from 'sentry/utils/requestError/requestError';
- import {useResizableDrawer} from 'sentry/utils/useResizableDrawer';
- import type {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/virtualizedViewManager';
- import {
- isAutogroupedNode,
- isMissingInstrumentationNode,
- isSpanNode,
- isTraceErrorNode,
- isTransactionNode,
- } from '../guards';
- import type {TraceTree, TraceTreeNode} from '../traceTree';
- import NodeDetail from './tabs/details';
- import {TraceLevelDetails} from './tabs/trace';
- const MIN_PANEL_HEIGHT = 31;
- const DEFAULT_PANEL_HEIGHT = 200;
- function getTabTitle(node: TraceTreeNode<TraceTree.NodeValue>) {
- if (isTransactionNode(node)) {
- return node.value['transaction.op'] + ' - ' + node.value.transaction;
- }
- if (isSpanNode(node)) {
- return node.value.op + ' - ' + node.value.description;
- }
- if (isAutogroupedNode(node)) {
- return t('Auto-Group');
- }
- if (isMissingInstrumentationNode(node)) {
- return t('Missing Instrumentation Span');
- }
- if (isTraceErrorNode(node)) {
- return node.value.title;
- }
- return t('Detail');
- }
- type TraceDrawerProps = {
- activeTab: 'trace' | 'node';
- location: Location;
- manager: VirtualizedViewManager;
- nodes: TraceTreeNode<TraceTree.NodeValue>[];
- organization: Organization;
- rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
- scrollToNode: (node: TraceTreeNode<TraceTree.NodeValue>) => void;
- setActiveTab: (tab: 'trace' | 'node') => void;
- traceEventView: EventView;
- traces: TraceSplitResults<TraceFullDetailed> | null;
- };
- function TraceDrawer(props: TraceDrawerProps) {
- const panelRef = useRef<HTMLDivElement>(null);
- const onResize = useCallback((newSize: number, maybeOldSize: number | undefined) => {
- if (!panelRef.current) {
- return;
- }
- panelRef.current.style.height = `${maybeOldSize ?? newSize}px`;
- panelRef.current.style.width = `100%`;
- }, []);
- const {onMouseDown} = useResizableDrawer({
- direction: 'up',
- initialSize: DEFAULT_PANEL_HEIGHT,
- min: MIN_PANEL_HEIGHT,
- sizeStorageKey: 'trace-drawer',
- onResize,
- });
- return (
- <PanelWrapper ref={panelRef}>
- <ResizeableHandle onMouseDown={onMouseDown} />
- <TabsContainer>
- {props.nodes.map((node, index) => {
- const title = getTabTitle(node);
- return (
- <Tab
- key={index}
- active={props.activeTab === 'node'}
- onClick={() => props.setActiveTab('node')}
- >
- <TabButton title={title}>{title}</TabButton>
- </Tab>
- );
- })}
- <Tab
- active={props.activeTab === 'trace'}
- onClick={() => props.setActiveTab('trace')}
- >
- <TabButton>{t('Trace')}</TabButton>
- </Tab>
- </TabsContainer>
- <Content>
- {props.activeTab === 'trace' ? (
- <TraceLevelDetails
- rootEventResults={props.rootEventResults}
- organization={props.organization}
- location={props.location}
- traces={props.traces}
- traceEventView={props.traceEventView}
- />
- ) : (
- props.nodes.map((node, index) => (
- <NodeDetail
- key={index}
- node={node}
- organization={props.organization}
- location={props.location}
- manager={props.manager}
- scrollToNode={props.scrollToNode}
- />
- ))
- )}
- </Content>
- </PanelWrapper>
- );
- }
- const ResizeableHandle = styled('div')`
- width: 100%;
- height: 8px;
- cursor: ns-resize;
- position: absolute;
- top: -4px;
- left: 0;
- z-index: 1;
- `;
- const PanelWrapper = styled('div')`
- display: flex;
- flex-direction: column;
- width: 100%;
- position: relative;
- border-top: 1px solid ${p => p.theme.border};
- bottom: 0;
- right: 0;
- background: ${p => p.theme.background};
- color: ${p => p.theme.textColor};
- text-align: left;
- z-index: ${p => p.theme.zIndex.sidebar - 1};
- `;
- const TabsContainer = styled('ul')`
- list-style-type: none;
- width: 100%;
- min-height: 30px;
- border-bottom: 1px solid ${p => p.theme.border};
- background-color: ${p => p.theme.backgroundSecondary};
- display: flex;
- align-items: center;
- justify-content: left;
- padding-left: ${space(2)};
- gap: ${space(1)};
- margin-bottom: 0;
- `;
- const Tab = styled('li')<{active: boolean}>`
- height: 100%;
- button {
- border-bottom: 2px solid ${p => (p.active ? p.theme.blue400 : 'transparent')};
- font-weight: ${p => (p.active ? 'bold' : 'normal')};
- }
- `;
- const TabButton = styled('button')`
- height: 100%;
- border: none;
- max-width: 160px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- border-top: 2px solid transparent;
- border-bottom: 2px solid transparent;
- border-radius: 0;
- margin: 0;
- padding: ${space(0.25)};
- font-size: ${p => p.theme.fontSizeSmall};
- color: ${p => p.theme.textColor};
- background: transparent;
- `;
- const Content = styled('div')`
- overflow: scroll;
- padding: ${space(1)};
- flex: 1;
- `;
- export default TraceDrawer;
|