spansTable.tsx 5.9 KB

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