spansTable.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import {Fragment} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import moment from 'moment-timezone';
  4. import Count from 'sentry/components/count';
  5. import {EmptyStreamWrapper} from 'sentry/components/emptyStateWarning';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import PerformanceDuration from 'sentry/components/performanceDuration';
  8. import {IconWarning} from 'sentry/icons/iconWarning';
  9. import {t, tct} from 'sentry/locale';
  10. import type {Organization} from 'sentry/types/organization';
  11. import {trackAnalytics} from 'sentry/utils/analytics';
  12. import {getUtcDateString} from 'sentry/utils/dates';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. import {usePageParams} from './hooks/usePageParams';
  16. import type {TraceResult} from './hooks/useTraces';
  17. import type {SpanResult} from './hooks/useTraceSpans';
  18. import {useTraceSpans} from './hooks/useTraceSpans';
  19. import {type Field, FIELDS, SORTS} from './data';
  20. import {
  21. SpanBreakdownSliceRenderer,
  22. SpanDescriptionRenderer,
  23. SpanIdRenderer,
  24. SpanTimeRenderer,
  25. TraceBreakdownContainer,
  26. } from './fieldRenderers';
  27. import {
  28. MoreMatchingSpans,
  29. SpanPanelContent,
  30. SpanTablePanelItem,
  31. StyledPanel,
  32. StyledPanelHeader,
  33. StyledPanelItem,
  34. StyledSpanPanelItem,
  35. } from './styles';
  36. import {areQueriesEmpty, getSecondaryNameFromSpan, getStylingSliceName} from './utils';
  37. const ONE_MINUTE = 60 * 1000; // in milliseconds
  38. export function SpanTable({
  39. trace,
  40. setHighlightedSliceName,
  41. }: {
  42. setHighlightedSliceName: (sliceName: string) => void;
  43. trace: TraceResult;
  44. }) {
  45. const location = useLocation();
  46. const organization = useOrganization();
  47. const {queries} = usePageParams(location);
  48. const spansQuery = useTraceSpans({
  49. trace,
  50. fields: [
  51. ...FIELDS,
  52. ...SORTS.map(field =>
  53. field.startsWith('-') ? (field.substring(1) as Field) : (field as Field)
  54. ),
  55. ],
  56. datetime: {
  57. // give a 1 minute buffer on each side so that start != end
  58. start: getUtcDateString(moment(trace.start - ONE_MINUTE)),
  59. end: getUtcDateString(moment(trace.end + ONE_MINUTE)),
  60. period: null,
  61. utc: true,
  62. },
  63. limit: 10,
  64. query: queries,
  65. sort: SORTS,
  66. });
  67. const isLoading = spansQuery.isPending;
  68. const isError = !isLoading && spansQuery.isError;
  69. const hasData = !isLoading && !isError && (spansQuery?.data?.data?.length ?? 0) > 0;
  70. const spans = spansQuery.data?.data ?? [];
  71. return (
  72. <SpanTablePanelItem span={7} overflow>
  73. <StyledPanel>
  74. <SpanPanelContent>
  75. <StyledPanelHeader align="left" lightText>
  76. {t('Span ID')}
  77. </StyledPanelHeader>
  78. <StyledPanelHeader align="left" lightText>
  79. {t('Span Description')}
  80. </StyledPanelHeader>
  81. <StyledPanelHeader align="right" lightText />
  82. <StyledPanelHeader align="right" lightText>
  83. {t('Span Duration')}
  84. </StyledPanelHeader>
  85. <StyledPanelHeader align="right" lightText>
  86. {t('Timestamp')}
  87. </StyledPanelHeader>
  88. {isLoading && (
  89. <StyledPanelItem span={5} overflow>
  90. <LoadingIndicator />
  91. </StyledPanelItem>
  92. )}
  93. {isError && ( // TODO: need an error state
  94. <StyledPanelItem span={5} overflow>
  95. <EmptyStreamWrapper>
  96. <IconWarning color="gray300" size="lg" />
  97. </EmptyStreamWrapper>
  98. </StyledPanelItem>
  99. )}
  100. {spans.map(span => (
  101. <SpanRow
  102. organization={organization}
  103. key={span.id}
  104. span={span}
  105. trace={trace}
  106. setHighlightedSliceName={setHighlightedSliceName}
  107. />
  108. ))}
  109. {hasData && spans.length < trace.matchingSpans && (
  110. <MoreMatchingSpans span={5}>
  111. {tct('[more][space]more [matching]spans can be found in the trace.', {
  112. more: <Count value={trace.matchingSpans - spans.length} />,
  113. space: <Fragment>&nbsp;</Fragment>,
  114. matching: areQueriesEmpty(queries) ? '' : 'matching ',
  115. })}
  116. </MoreMatchingSpans>
  117. )}
  118. </SpanPanelContent>
  119. </StyledPanel>
  120. </SpanTablePanelItem>
  121. );
  122. }
  123. function SpanRow({
  124. organization,
  125. span,
  126. trace,
  127. setHighlightedSliceName,
  128. }: {
  129. organization: Organization;
  130. setHighlightedSliceName: (sliceName: string) => void;
  131. span: SpanResult<Field>;
  132. trace: TraceResult;
  133. }) {
  134. const theme = useTheme();
  135. return (
  136. <Fragment>
  137. <StyledSpanPanelItem align="right">
  138. <SpanIdRenderer
  139. projectSlug={span.project}
  140. transactionId={span['transaction.id']}
  141. spanId={span.id}
  142. traceId={trace.trace}
  143. timestamp={span.timestamp}
  144. onClick={() =>
  145. trackAnalytics('trace_explorer.open_trace_span', {
  146. organization,
  147. source: 'trace explorer',
  148. })
  149. }
  150. />
  151. </StyledSpanPanelItem>
  152. <StyledSpanPanelItem align="left" overflow>
  153. <SpanDescriptionRenderer span={span} />
  154. </StyledSpanPanelItem>
  155. <StyledSpanPanelItem align="right" onMouseLeave={() => setHighlightedSliceName('')}>
  156. <TraceBreakdownContainer>
  157. <SpanBreakdownSliceRenderer
  158. sliceName={span.project}
  159. sliceSecondaryName={getSecondaryNameFromSpan(span)}
  160. sliceStart={Math.ceil(span['precise.start_ts'] * 1000)}
  161. sliceEnd={Math.floor(span['precise.finish_ts'] * 1000)}
  162. trace={trace}
  163. theme={theme}
  164. onMouseEnter={() =>
  165. setHighlightedSliceName(
  166. getStylingSliceName(span.project, getSecondaryNameFromSpan(span)) ?? ''
  167. )
  168. }
  169. />
  170. </TraceBreakdownContainer>
  171. </StyledSpanPanelItem>
  172. <StyledSpanPanelItem align="right">
  173. <PerformanceDuration milliseconds={span['span.duration']} abbreviation />
  174. </StyledSpanPanelItem>
  175. <StyledSpanPanelItem align="right">
  176. <SpanTimeRenderer
  177. timestamp={span['precise.finish_ts'] * 1000}
  178. tooltipShowSeconds
  179. />
  180. </StyledSpanPanelItem>
  181. </Fragment>
  182. );
  183. }