spansTable.tsx 6.4 KB

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