spansTable.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import {Fragment, useMemo, useRef} from 'react';
  2. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  3. import {GridResizer} from 'sentry/components/gridEditable/styles';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import Pagination from 'sentry/components/pagination';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {IconArrow} from 'sentry/icons/iconArrow';
  8. import {IconWarning} from 'sentry/icons/iconWarning';
  9. import {t} from 'sentry/locale';
  10. import type {Confidence} from 'sentry/types/organization';
  11. import {defined} from 'sentry/utils';
  12. import {fieldAlignment, prettifyTagKey} from 'sentry/utils/discover/fields';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {
  15. Table,
  16. TableBody,
  17. TableBodyCell,
  18. TableHead,
  19. TableHeadCell,
  20. TableHeadCellContent,
  21. TableRow,
  22. TableStatus,
  23. useTableStyles,
  24. } from 'sentry/views/explore/components/table';
  25. import {
  26. useExploreDataset,
  27. useExploreFields,
  28. useExploreQuery,
  29. useExploreSortBys,
  30. useExploreTitle,
  31. useExploreVisualizes,
  32. useSetExploreSortBys,
  33. } from 'sentry/views/explore/contexts/pageParamsContext';
  34. import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
  35. import {useAnalytics} from 'sentry/views/explore/hooks/useAnalytics';
  36. import type {SpansTableResult} from 'sentry/views/explore/hooks/useExploreSpansTable';
  37. import {FieldRenderer} from './fieldRenderer';
  38. interface SpansTableProps {
  39. confidences: Confidence[];
  40. spansTableResult: SpansTableResult;
  41. }
  42. export function SpansTable({confidences, spansTableResult}: SpansTableProps) {
  43. const dataset = useExploreDataset();
  44. const title = useExploreTitle();
  45. const fields = useExploreFields();
  46. const sortBys = useExploreSortBys();
  47. const setSortBys = useSetExploreSortBys();
  48. const query = useExploreQuery();
  49. const visualizes = useExploreVisualizes();
  50. const organization = useOrganization();
  51. const visibleFields = useMemo(
  52. () => (fields.includes('id') ? fields : ['id', ...fields]),
  53. [fields]
  54. );
  55. const {result, eventView} = spansTableResult;
  56. const columnsFromEventView = useMemo(() => eventView.getColumns(), [eventView]);
  57. useAnalytics({
  58. dataset,
  59. resultLength: result.data?.length,
  60. resultMode: 'span samples',
  61. resultStatus: result.status,
  62. visualizes,
  63. organization,
  64. columns: fields,
  65. userQuery: query,
  66. confidences,
  67. title,
  68. });
  69. const tableRef = useRef<HTMLTableElement>(null);
  70. const {initialTableStyles, onResizeMouseDown} = useTableStyles(
  71. visibleFields,
  72. tableRef,
  73. {
  74. minimumColumnWidth: 50,
  75. }
  76. );
  77. const meta = result.meta ?? {};
  78. const numberTags = useSpanTags('number');
  79. const stringTags = useSpanTags('string');
  80. return (
  81. <Fragment>
  82. <Table ref={tableRef} styles={initialTableStyles}>
  83. <TableHead>
  84. <TableRow>
  85. {visibleFields.map((field, i) => {
  86. // Hide column names before alignment is determined
  87. if (result.isPending) {
  88. return <TableHeadCell key={i} isFirst={i === 0} />;
  89. }
  90. const fieldType = meta.fields?.[field];
  91. const align = fieldAlignment(field, fieldType);
  92. const tag = stringTags[field] ?? numberTags[field] ?? null;
  93. const direction = sortBys.find(s => s.field === field)?.kind;
  94. function updateSort() {
  95. const kind = direction === 'desc' ? 'asc' : 'desc';
  96. setSortBys([{field, kind}]);
  97. }
  98. const label = tag?.name ?? prettifyTagKey(field);
  99. return (
  100. <TableHeadCell align={align} key={i} isFirst={i === 0}>
  101. <TableHeadCellContent onClick={updateSort}>
  102. <Tooltip showOnlyOnOverflow title={label}>
  103. {label}
  104. </Tooltip>
  105. {defined(direction) && (
  106. <IconArrow
  107. size="xs"
  108. direction={
  109. direction === 'desc'
  110. ? 'down'
  111. : direction === 'asc'
  112. ? 'up'
  113. : undefined
  114. }
  115. />
  116. )}
  117. </TableHeadCellContent>
  118. {i !== visibleFields.length - 1 && (
  119. <GridResizer
  120. dataRows={
  121. !result.isError && !result.isPending && result.data
  122. ? result.data.length
  123. : 0
  124. }
  125. onMouseDown={e => onResizeMouseDown(e, i)}
  126. />
  127. )}
  128. </TableHeadCell>
  129. );
  130. })}
  131. </TableRow>
  132. </TableHead>
  133. <TableBody>
  134. {result.isPending ? (
  135. <TableStatus>
  136. <LoadingIndicator />
  137. </TableStatus>
  138. ) : result.isError ? (
  139. <TableStatus>
  140. <IconWarning data-test-id="error-indicator" color="gray300" size="lg" />
  141. </TableStatus>
  142. ) : result.isFetched && result.data?.length ? (
  143. result.data?.map((row, i) => (
  144. <TableRow key={i}>
  145. {visibleFields.map((field, j) => {
  146. return (
  147. <TableBodyCell key={j}>
  148. <FieldRenderer
  149. column={columnsFromEventView[j]!}
  150. data={row}
  151. unit={meta?.units?.[field]}
  152. meta={meta}
  153. />
  154. </TableBodyCell>
  155. );
  156. })}
  157. </TableRow>
  158. ))
  159. ) : (
  160. <TableStatus>
  161. <EmptyStateWarning>
  162. <p>{t('No spans found')}</p>
  163. </EmptyStateWarning>
  164. </TableStatus>
  165. )}
  166. </TableBody>
  167. </Table>
  168. <Pagination pageLinks={result.pageLinks} />
  169. </Fragment>
  170. );
  171. }