spansTable.tsx 5.2 KB

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