spansTable.tsx 5.9 KB

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