spansTable.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import type {Dispatch, SetStateAction} from 'react';
  2. import {Fragment, useEffect, useMemo} from 'react';
  3. import styled from '@emotion/styled';
  4. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import Pagination from 'sentry/components/pagination';
  7. import {IconArrow} from 'sentry/icons/iconArrow';
  8. import {IconWarning} from 'sentry/icons/iconWarning';
  9. import {t} from 'sentry/locale';
  10. import type {NewQuery} from 'sentry/types/organization';
  11. import {defined} from 'sentry/utils';
  12. import EventView from 'sentry/utils/discover/eventView';
  13. import {fieldAlignment} from 'sentry/utils/discover/fields';
  14. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import usePageFilters from 'sentry/utils/usePageFilters';
  17. import {
  18. Table,
  19. TableBody,
  20. TableBodyCell,
  21. TableHead,
  22. TableHeadCell,
  23. TableRow,
  24. TableStatus,
  25. useTableStyles,
  26. } from 'sentry/views/explore/components/table';
  27. import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
  28. import {useAnalytics} from 'sentry/views/explore/hooks/useAnalytics';
  29. import {useDataset} from 'sentry/views/explore/hooks/useDataset';
  30. import {useSampleFields} from 'sentry/views/explore/hooks/useSampleFields';
  31. import {useSorts} from 'sentry/views/explore/hooks/useSorts';
  32. import {useUserQuery} from 'sentry/views/explore/hooks/useUserQuery';
  33. import {useVisualizes} from 'sentry/views/explore/hooks/useVisualizes';
  34. import {useSpansQuery} from 'sentry/views/insights/common/queries/useSpansQuery';
  35. import {FieldRenderer} from './fieldRenderer';
  36. interface SpansTableProps {
  37. setError: Dispatch<SetStateAction<string>>;
  38. }
  39. export function SpansTable({setError}: SpansTableProps) {
  40. const {selection} = usePageFilters();
  41. const [dataset] = useDataset();
  42. const [fields] = useSampleFields();
  43. const [sorts, setSorts] = useSorts({fields});
  44. const [query] = useUserQuery();
  45. const [visualizes] = useVisualizes();
  46. const organization = useOrganization();
  47. const eventView = useMemo(() => {
  48. const queryFields = [
  49. ...fields,
  50. 'project',
  51. 'trace',
  52. 'transaction.span_id',
  53. 'span_id',
  54. 'timestamp',
  55. ];
  56. const search = new MutableSearch(query);
  57. // Filtering out all spans with op like 'ui.interaction*' which aren't
  58. // embedded under transactions. The trace view does not support rendering
  59. // such spans yet.
  60. search.addFilterValues('!transaction.span_id', ['00']);
  61. const discoverQuery: NewQuery = {
  62. id: undefined,
  63. name: 'Explore - Span Samples',
  64. fields: queryFields,
  65. orderby: sorts.map(sort => `${sort.kind === 'desc' ? '-' : ''}${sort.field}`),
  66. query: search.formatString(),
  67. version: 2,
  68. dataset,
  69. };
  70. return EventView.fromNewQueryWithPageFilters(discoverQuery, selection);
  71. }, [dataset, fields, sorts, query, selection]);
  72. const columns = useMemo(() => eventView.getColumns(), [eventView]);
  73. const result = useSpansQuery({
  74. eventView,
  75. initialData: [],
  76. referrer: 'api.explore.spans-samples-table',
  77. allowAggregateConditions: false,
  78. });
  79. useEffect(() => {
  80. setError(result.error?.message ?? '');
  81. }, [setError, result.error?.message]);
  82. useAnalytics({
  83. resultLength: result.data?.length,
  84. resultMode: 'span samples',
  85. resultStatus: result.status,
  86. visualizes,
  87. organization,
  88. columns: fields,
  89. userQuery: query,
  90. });
  91. const {tableStyles} = useTableStyles({
  92. items: fields.map(field => {
  93. return {
  94. label: field,
  95. value: field,
  96. };
  97. }),
  98. });
  99. const meta = result.meta ?? {};
  100. const numberTags = useSpanTags('number');
  101. const stringTags = useSpanTags('string');
  102. return (
  103. <Fragment>
  104. <Table style={tableStyles}>
  105. <TableHead>
  106. <TableRow>
  107. {fields.map((field, i) => {
  108. // Hide column names before alignment is determined
  109. if (result.isPending) {
  110. return <TableHeadCell key={i} isFirst={i === 0} />;
  111. }
  112. const fieldType = meta.fields?.[field];
  113. const align = fieldAlignment(field, fieldType);
  114. const tag = stringTags[field] ?? numberTags[field] ?? null;
  115. const direction = sorts.find(s => s.field === field)?.kind;
  116. function updateSort() {
  117. const kind = direction === 'desc' ? 'asc' : 'desc';
  118. setSorts([{field, kind}]);
  119. }
  120. return (
  121. <StyledTableHeadCell
  122. align={align}
  123. key={i}
  124. isFirst={i === 0}
  125. onClick={updateSort}
  126. >
  127. <span>{tag?.name ?? field}</span>
  128. {defined(direction) && (
  129. <IconArrow
  130. size="xs"
  131. direction={
  132. direction === 'desc'
  133. ? 'down'
  134. : direction === 'asc'
  135. ? 'up'
  136. : undefined
  137. }
  138. />
  139. )}
  140. </StyledTableHeadCell>
  141. );
  142. })}
  143. </TableRow>
  144. </TableHead>
  145. <TableBody>
  146. {result.isPending ? (
  147. <TableStatus>
  148. <LoadingIndicator />
  149. </TableStatus>
  150. ) : result.isError ? (
  151. <TableStatus>
  152. <IconWarning data-test-id="error-indicator" color="gray300" size="lg" />
  153. </TableStatus>
  154. ) : result.isFetched && result.data?.length ? (
  155. result.data?.map((row, i) => (
  156. <TableRow key={i}>
  157. {fields.map((field, j) => {
  158. return (
  159. <TableBodyCell key={j}>
  160. <FieldRenderer
  161. column={columns[j]}
  162. data={row}
  163. unit={meta?.units?.[field]}
  164. meta={meta}
  165. />
  166. </TableBodyCell>
  167. );
  168. })}
  169. </TableRow>
  170. ))
  171. ) : (
  172. <TableStatus>
  173. <EmptyStateWarning>
  174. <p>{t('No spans found')}</p>
  175. </EmptyStateWarning>
  176. </TableStatus>
  177. )}
  178. </TableBody>
  179. </Table>
  180. <Pagination pageLinks={result.pageLinks} />
  181. </Fragment>
  182. );
  183. }
  184. const StyledTableHeadCell = styled(TableHeadCell)`
  185. cursor: pointer;
  186. `;