sampleTable.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import {useCallback} from 'react';
  2. import {Link} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {LinkButton} from 'sentry/components/button';
  5. import GridEditable, {
  6. COL_WIDTH_UNDEFINED,
  7. GridColumnHeader,
  8. GridColumnOrder,
  9. } from 'sentry/components/gridEditable';
  10. import {Tooltip} from 'sentry/components/tooltip';
  11. import {IconPlay, IconProfiling} from 'sentry/icons';
  12. import {t} from 'sentry/locale';
  13. import {MRI} from 'sentry/types';
  14. import {getDuration} from 'sentry/utils/formatters';
  15. import {useMetricsSpans} from 'sentry/utils/metrics/useMetricsCodeLocations';
  16. import {useLocation} from 'sentry/utils/useLocation';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import useProjects from 'sentry/utils/useProjects';
  19. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  20. import {MetricRange, MetricSpan} from '../../utils/metrics/index';
  21. export type SamplesTableProps = MetricRange & {
  22. mri?: MRI;
  23. query?: string;
  24. };
  25. type Column = GridColumnHeader<keyof MetricSpan>;
  26. const columnOrder: GridColumnOrder<keyof MetricSpan>[] = [
  27. {key: 'spanId', width: COL_WIDTH_UNDEFINED, name: 'Event ID'},
  28. {key: 'duration', width: COL_WIDTH_UNDEFINED, name: 'Duration'},
  29. {key: 'traceId', width: COL_WIDTH_UNDEFINED, name: 'Trace ID'},
  30. {key: 'profileId', width: COL_WIDTH_UNDEFINED, name: 'Profile'},
  31. {key: 'replayId', width: COL_WIDTH_UNDEFINED, name: 'Replay'},
  32. ];
  33. export function SampleTable({mri, ...metricMetaOptions}: SamplesTableProps) {
  34. const location = useLocation();
  35. const organization = useOrganization();
  36. const {projects} = useProjects();
  37. const {data, isFetching} = useMetricsSpans(mri, metricMetaOptions);
  38. const getProjectSlug = useCallback(
  39. (projectId: number) => {
  40. return projects.find(p => parseInt(p.id, 10) === projectId)?.slug;
  41. },
  42. [projects]
  43. );
  44. const rows = data?.metrics
  45. .map(m => m.metricSpans)
  46. .flat()
  47. .filter(Boolean) as MetricSpan[];
  48. function renderHeadCell(col: Column) {
  49. if (col.key === 'profileId' || col.key === 'replayId') {
  50. return <AlignCenter>{col.name}</AlignCenter>;
  51. }
  52. return <span>{col.name}</span>;
  53. }
  54. function renderBodyCell(col: Column, row: MetricSpan) {
  55. const {key} = col;
  56. if (!row[key]) {
  57. return <AlignCenter>{'\u2014'}</AlignCenter>;
  58. }
  59. if (key === 'spanId') {
  60. return (
  61. <span>
  62. <Link
  63. to={normalizeUrl(
  64. `/organizations/${organization.slug}/performance/${getProjectSlug(
  65. row.projectId
  66. )}:${row.transactionId}/#span-${row.spanId}`
  67. )}
  68. >
  69. {row.spanId.slice(0, 8)}
  70. </Link>
  71. </span>
  72. );
  73. }
  74. if (key === 'duration') {
  75. // We get duration in miliseconds, but getDuration expects seconds
  76. return <span>{getDuration(row.duration / 1000, 2, true)}</span>;
  77. }
  78. if (key === 'traceId') {
  79. return (
  80. <span>
  81. <Link
  82. to={normalizeUrl(
  83. `/organizations/${organization.slug}/performance/trace/${row.traceId}/`
  84. )}
  85. >
  86. {row.traceId.slice(0, 8)}
  87. </Link>
  88. </span>
  89. );
  90. }
  91. if (key === 'profileId') {
  92. return (
  93. <AlignCenter>
  94. <Tooltip title={t('View Profile')}>
  95. <LinkButton
  96. to={normalizeUrl(
  97. `/organizations/${organization.slug}/profiling/profile/${getProjectSlug(
  98. row.projectId
  99. )}/${row.profileId}/flamegraph/`
  100. )}
  101. size="xs"
  102. >
  103. <IconProfiling size="xs" />
  104. </LinkButton>
  105. </Tooltip>
  106. </AlignCenter>
  107. );
  108. }
  109. if (key === 'replayId') {
  110. return (
  111. <AlignCenter>
  112. <Tooltip title={t('View Replay')}>
  113. <LinkButton
  114. to={normalizeUrl(
  115. `/organizations/${organization.slug}/replays/${row.replayId}/`
  116. )}
  117. size="xs"
  118. >
  119. <IconPlay size="xs" />
  120. </LinkButton>
  121. </Tooltip>
  122. </AlignCenter>
  123. );
  124. }
  125. return <span>{row[col.key]}</span>;
  126. }
  127. return (
  128. <GridEditable
  129. isLoading={isFetching}
  130. columnOrder={columnOrder}
  131. columnSortBy={[]}
  132. data={rows}
  133. grid={{
  134. renderHeadCell,
  135. renderBodyCell,
  136. }}
  137. emptyMessage={mri ? t('No samples found') : t('Choose a metric to display data.')}
  138. location={location}
  139. />
  140. );
  141. }
  142. const AlignCenter = styled('span')`
  143. display: block;
  144. margin: auto;
  145. text-align: center;
  146. width: 100%;
  147. `;