spansTable.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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, metricsMax, metricsMin, metricsOp, metricsQuery, mri} =
  48. usePageParams(location);
  49. const hasMetric = metricsOp && mri;
  50. const spansQuery = useTraceSpans({
  51. trace,
  52. fields: [
  53. ...FIELDS,
  54. ...SORTS.map(field =>
  55. field.startsWith('-') ? (field.substring(1) as Field) : (field as Field)
  56. ),
  57. ],
  58. datetime: {
  59. // give a 1 minute buffer on each side so that start != end
  60. start: getUtcDateString(moment(trace.start - ONE_MINUTE)),
  61. end: getUtcDateString(moment(trace.end + ONE_MINUTE)),
  62. period: null,
  63. utc: true,
  64. },
  65. limit: 10,
  66. query: queries,
  67. sort: SORTS,
  68. mri: hasMetric ? mri : undefined,
  69. metricsMax: hasMetric ? metricsMax : undefined,
  70. metricsMin: hasMetric ? metricsMin : undefined,
  71. metricsOp: hasMetric ? metricsOp : undefined,
  72. metricsQuery: hasMetric ? metricsQuery : undefined,
  73. });
  74. const isLoading = spansQuery.isPending;
  75. const isError = !isLoading && spansQuery.isError;
  76. const hasData = !isLoading && !isError && (spansQuery?.data?.data?.length ?? 0) > 0;
  77. const spans = spansQuery.data?.data ?? [];
  78. return (
  79. <SpanTablePanelItem span={7} overflow>
  80. <StyledPanel>
  81. <SpanPanelContent>
  82. <StyledPanelHeader align="left" lightText>
  83. {t('Span ID')}
  84. </StyledPanelHeader>
  85. <StyledPanelHeader align="left" lightText>
  86. {t('Span Description')}
  87. </StyledPanelHeader>
  88. <StyledPanelHeader align="right" lightText />
  89. <StyledPanelHeader align="right" lightText>
  90. {t('Span Duration')}
  91. </StyledPanelHeader>
  92. <StyledPanelHeader align="right" lightText>
  93. {t('Timestamp')}
  94. </StyledPanelHeader>
  95. {isLoading && (
  96. <StyledPanelItem span={5} overflow>
  97. <LoadingIndicator />
  98. </StyledPanelItem>
  99. )}
  100. {isError && ( // TODO: need an error state
  101. <StyledPanelItem span={5} overflow>
  102. <EmptyStreamWrapper>
  103. <IconWarning color="gray300" size="lg" />
  104. </EmptyStreamWrapper>
  105. </StyledPanelItem>
  106. )}
  107. {spans.map(span => (
  108. <SpanRow
  109. organization={organization}
  110. key={span.id}
  111. span={span}
  112. trace={trace}
  113. setHighlightedSliceName={setHighlightedSliceName}
  114. />
  115. ))}
  116. {hasData && spans.length < trace.matchingSpans && (
  117. <MoreMatchingSpans span={5}>
  118. {tct('[more][space]more [matching]spans can be found in the trace.', {
  119. more: <Count value={trace.matchingSpans - spans.length} />,
  120. space: <Fragment>&nbsp;</Fragment>,
  121. matching: areQueriesEmpty(queries) ? '' : 'matching ',
  122. })}
  123. </MoreMatchingSpans>
  124. )}
  125. </SpanPanelContent>
  126. </StyledPanel>
  127. </SpanTablePanelItem>
  128. );
  129. }
  130. function SpanRow({
  131. organization,
  132. span,
  133. trace,
  134. setHighlightedSliceName,
  135. }: {
  136. organization: Organization;
  137. setHighlightedSliceName: (sliceName: string) => void;
  138. span: SpanResult<Field>;
  139. trace: TraceResult;
  140. }) {
  141. const theme = useTheme();
  142. return (
  143. <Fragment>
  144. <StyledSpanPanelItem align="right">
  145. <SpanIdRenderer
  146. projectSlug={span.project}
  147. transactionId={span['transaction.id']}
  148. spanId={span.id}
  149. traceId={trace.trace}
  150. timestamp={span.timestamp}
  151. onClick={() =>
  152. trackAnalytics('trace_explorer.open_trace_span', {
  153. organization,
  154. })
  155. }
  156. />
  157. </StyledSpanPanelItem>
  158. <StyledSpanPanelItem align="left" overflow>
  159. <SpanDescriptionRenderer span={span} />
  160. </StyledSpanPanelItem>
  161. <StyledSpanPanelItem align="right" onMouseLeave={() => setHighlightedSliceName('')}>
  162. <TraceBreakdownContainer>
  163. <SpanBreakdownSliceRenderer
  164. sliceName={span.project}
  165. sliceSecondaryName={getSecondaryNameFromSpan(span)}
  166. sliceStart={Math.ceil(span['precise.start_ts'] * 1000)}
  167. sliceEnd={Math.floor(span['precise.finish_ts'] * 1000)}
  168. trace={trace}
  169. theme={theme}
  170. onMouseEnter={() =>
  171. setHighlightedSliceName(
  172. getStylingSliceName(span.project, getSecondaryNameFromSpan(span)) ?? ''
  173. )
  174. }
  175. />
  176. </TraceBreakdownContainer>
  177. </StyledSpanPanelItem>
  178. <StyledSpanPanelItem align="right">
  179. <PerformanceDuration milliseconds={span['span.duration']} abbreviation />
  180. </StyledSpanPanelItem>
  181. <StyledSpanPanelItem align="right">
  182. <SpanTimeRenderer
  183. timestamp={span['precise.finish_ts'] * 1000}
  184. tooltipShowSeconds
  185. />
  186. </StyledSpanPanelItem>
  187. </Fragment>
  188. );
  189. }