spanSamplesTable.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import styled from '@emotion/styled';
  2. import {LinkButton} from 'sentry/components/button';
  3. import type {GridColumnHeader} from 'sentry/components/gridEditable';
  4. import GridEditable, {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  5. import Link from 'sentry/components/links/link';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {IconProfiling} from 'sentry/icons/iconProfiling';
  8. import {t} from 'sentry/locale';
  9. import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  13. import {DurationComparisonCell} from 'sentry/views/starfish/components/samplesTable/common';
  14. import {DurationCell} from 'sentry/views/starfish/components/tableCells/durationCell';
  15. import ResourceSizeCell from 'sentry/views/starfish/components/tableCells/resourceSizeCell';
  16. import {
  17. OverflowEllipsisTextContainer,
  18. TextAlignRight,
  19. } from 'sentry/views/starfish/components/textAlign';
  20. import type {SpanSample} from 'sentry/views/starfish/queries/useSpanSamples';
  21. import {SpanMetricsField} from 'sentry/views/starfish/types';
  22. const {HTTP_RESPONSE_CONTENT_LENGTH} = SpanMetricsField;
  23. type Keys =
  24. | 'transaction_id'
  25. | 'span_id'
  26. | 'profile_id'
  27. | 'timestamp'
  28. | 'duration'
  29. | 'p95_comparison'
  30. | 'avg_comparison'
  31. | 'http.response_content_length';
  32. export type SamplesTableColumnHeader = GridColumnHeader<Keys>;
  33. export const DEFAULT_COLUMN_ORDER: SamplesTableColumnHeader[] = [
  34. {
  35. key: 'span_id',
  36. name: 'Span ID',
  37. width: COL_WIDTH_UNDEFINED,
  38. },
  39. {
  40. key: 'duration',
  41. name: 'Span Duration',
  42. width: COL_WIDTH_UNDEFINED,
  43. },
  44. {
  45. key: 'avg_comparison',
  46. name: 'Compared to Average',
  47. width: COL_WIDTH_UNDEFINED,
  48. },
  49. ];
  50. type SpanTableRow = {
  51. op: string;
  52. transaction: {
  53. id: string;
  54. 'project.name': string;
  55. timestamp: string;
  56. trace: string;
  57. 'transaction.duration': number;
  58. };
  59. } & SpanSample;
  60. type Props = {
  61. avg: number;
  62. data: SpanTableRow[];
  63. isLoading: boolean;
  64. columnOrder?: SamplesTableColumnHeader[];
  65. highlightedSpanId?: string;
  66. onMouseLeaveSample?: () => void;
  67. onMouseOverSample?: (sample: SpanSample) => void;
  68. };
  69. export function SpanSamplesTable({
  70. isLoading,
  71. data,
  72. avg,
  73. highlightedSpanId,
  74. onMouseLeaveSample,
  75. onMouseOverSample,
  76. columnOrder,
  77. }: Props) {
  78. const location = useLocation();
  79. const organization = useOrganization();
  80. function renderHeadCell(column: GridColumnHeader): React.ReactNode {
  81. if (
  82. column.key === 'p95_comparison' ||
  83. column.key === 'avg_comparison' ||
  84. column.key === 'duration'
  85. ) {
  86. return (
  87. <TextAlignRight>
  88. <OverflowEllipsisTextContainer>{column.name}</OverflowEllipsisTextContainer>
  89. </TextAlignRight>
  90. );
  91. }
  92. return <OverflowEllipsisTextContainer>{column.name}</OverflowEllipsisTextContainer>;
  93. }
  94. function renderBodyCell(column: GridColumnHeader, row: SpanTableRow): React.ReactNode {
  95. if (column.key === 'transaction_id') {
  96. return (
  97. <Link
  98. to={generateLinkToEventInTraceView({
  99. eventId: row['transaction.id'],
  100. timestamp: row.timestamp,
  101. traceSlug: row.transaction?.trace,
  102. projectSlug: row.project,
  103. organization,
  104. location,
  105. spanId: row.span_id,
  106. })}
  107. >
  108. {row['transaction.id'].slice(0, 8)}
  109. </Link>
  110. );
  111. }
  112. if (column.key === 'span_id') {
  113. return (
  114. <Link
  115. to={generateLinkToEventInTraceView({
  116. eventId: row['transaction.id'],
  117. timestamp: row.timestamp,
  118. traceSlug: row.transaction?.trace,
  119. projectSlug: row.project,
  120. organization,
  121. location,
  122. spanId: row.span_id,
  123. })}
  124. >
  125. {row.span_id}
  126. </Link>
  127. );
  128. }
  129. if (column.key === HTTP_RESPONSE_CONTENT_LENGTH) {
  130. const size = parseInt(row[HTTP_RESPONSE_CONTENT_LENGTH], 10);
  131. return <ResourceSizeCell bytes={size} />;
  132. }
  133. if (column.key === 'profile_id') {
  134. return (
  135. <IconWrapper>
  136. {row.profile_id ? (
  137. <Tooltip title={t('View Profile')}>
  138. <LinkButton
  139. to={normalizeUrl(
  140. `/organizations/${organization.slug}/profiling/profile/${row.project}/${row.profile_id}/flamegraph/?spanId=${row.span_id}`
  141. )}
  142. size="xs"
  143. >
  144. <IconProfiling size="xs" />
  145. </LinkButton>
  146. </Tooltip>
  147. ) : (
  148. <div>(no value)</div>
  149. )}
  150. </IconWrapper>
  151. );
  152. }
  153. if (column.key === 'duration') {
  154. return <DurationCell milliseconds={row['span.self_time']} />;
  155. }
  156. if (column.key === 'avg_comparison') {
  157. return (
  158. <DurationComparisonCell
  159. duration={row['span.self_time']}
  160. compareToDuration={avg}
  161. />
  162. );
  163. }
  164. return <span>{row[column.key]}</span>;
  165. }
  166. return (
  167. <GridEditable
  168. isLoading={isLoading}
  169. data={data}
  170. columnOrder={columnOrder ?? DEFAULT_COLUMN_ORDER}
  171. columnSortBy={[]}
  172. onRowMouseOver={onMouseOverSample}
  173. onRowMouseOut={onMouseLeaveSample}
  174. highlightedRowKey={data.findIndex(sample => sample.span_id === highlightedSpanId)}
  175. grid={{
  176. renderHeadCell,
  177. renderBodyCell,
  178. }}
  179. location={location}
  180. />
  181. );
  182. }
  183. const IconWrapper = styled('div')`
  184. text-align: right;
  185. width: 100%;
  186. height: 26px;
  187. `;