fieldRenderer.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import styled from '@emotion/styled';
  2. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  3. import ExternalLink from 'sentry/components/links/externalLink';
  4. import Link from 'sentry/components/links/link';
  5. import TimeSince from 'sentry/components/timeSince';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {space} from 'sentry/styles/space';
  8. import type {TableDataRow} from 'sentry/utils/discover/discoverQuery';
  9. import type {EventData, MetaType} from 'sentry/utils/discover/eventView';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import {getFieldRenderer, nullableValue} from 'sentry/utils/discover/fieldRenderers';
  12. import {Container} from 'sentry/utils/discover/styles';
  13. import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
  14. import {getShortEventId} from 'sentry/utils/events';
  15. import {generateProfileFlamechartRouteWithQuery} from 'sentry/utils/profiling/routes';
  16. import {isUrl} from 'sentry/utils/string/isUrl';
  17. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  18. import {useLocation} from 'sentry/utils/useLocation';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import useProjects from 'sentry/utils/useProjects';
  21. import CellAction, {updateQuery} from 'sentry/views/discover/table/cellAction';
  22. import type {TableColumn} from 'sentry/views/discover/table/types';
  23. import {
  24. useExploreQuery,
  25. useSetExploreQuery,
  26. } from 'sentry/views/explore/contexts/pageParamsContext';
  27. import {
  28. useReadQueriesFromLocation,
  29. useUpdateQueryAtIndex,
  30. } from 'sentry/views/explore/multiQueryMode/locationUtils';
  31. import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs';
  32. import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
  33. import {ALLOWED_CELL_ACTIONS} from '../components/table';
  34. interface FieldProps {
  35. column: TableColumn<keyof TableDataRow>;
  36. data: EventData;
  37. meta: MetaType;
  38. unit?: string;
  39. }
  40. export function FieldRenderer({data, meta, unit, column}: FieldProps) {
  41. const userQuery = useExploreQuery();
  42. const setUserQuery = useSetExploreQuery();
  43. return (
  44. <BaseExploreFieldRenderer
  45. data={data}
  46. meta={meta}
  47. unit={unit}
  48. column={column}
  49. userQuery={userQuery}
  50. setUserQuery={setUserQuery}
  51. />
  52. );
  53. }
  54. export interface MultiQueryFieldProps extends FieldProps {
  55. index: number;
  56. }
  57. export function MultiQueryFieldRenderer({
  58. data,
  59. meta,
  60. unit,
  61. column,
  62. index,
  63. }: MultiQueryFieldProps) {
  64. const queries = useReadQueriesFromLocation();
  65. const userQuery = queries[index]?.query ?? '';
  66. const updateQuerySearch = useUpdateQueryAtIndex(index);
  67. return (
  68. <BaseExploreFieldRenderer
  69. data={data}
  70. meta={meta}
  71. unit={unit}
  72. column={column}
  73. userQuery={userQuery}
  74. setUserQuery={(query: string) => updateQuerySearch({query})}
  75. />
  76. );
  77. }
  78. interface BaseFieldProps extends FieldProps {
  79. setUserQuery: (query: string) => void;
  80. userQuery: string;
  81. }
  82. function BaseExploreFieldRenderer({
  83. data,
  84. meta,
  85. unit,
  86. column,
  87. userQuery,
  88. setUserQuery,
  89. }: BaseFieldProps) {
  90. const location = useLocation();
  91. const organization = useOrganization();
  92. const dateSelection = EventView.fromLocation(location).normalizeDateSelection(location);
  93. const query = new MutableSearch(userQuery);
  94. const field = column.name;
  95. const renderer = getExploreFieldRenderer(field, meta);
  96. let rendered = renderer(data, {
  97. location,
  98. organization,
  99. unit,
  100. });
  101. if (field === 'timestamp') {
  102. const date = new Date(data.timestamp);
  103. rendered = <StyledTimeSince unitStyle="extraShort" date={date} tooltipShowSeconds />;
  104. }
  105. if (field === 'trace') {
  106. const target = getTraceDetailsUrl({
  107. traceSlug: data.trace,
  108. timestamp: data.timestamp,
  109. organization,
  110. dateSelection,
  111. location,
  112. source: TraceViewSources.TRACES,
  113. });
  114. rendered = <Link to={target}>{rendered}</Link>;
  115. }
  116. if (['id', 'span_id', 'transaction.id'].includes(field)) {
  117. const spanId = field === 'transaction.id' ? undefined : (data.span_id ?? data.id);
  118. const target = generateLinkToEventInTraceView({
  119. projectSlug: data.project,
  120. traceSlug: data.trace,
  121. timestamp: data.timestamp,
  122. targetId: data['transaction.span_id'],
  123. eventId: undefined,
  124. organization,
  125. location,
  126. spanId,
  127. source: TraceViewSources.TRACES,
  128. });
  129. rendered = <Link to={target}>{rendered}</Link>;
  130. }
  131. if (field === 'profile.id') {
  132. const target = generateProfileFlamechartRouteWithQuery({
  133. organization,
  134. projectSlug: data.project,
  135. profileId: data['profile.id'],
  136. });
  137. rendered = <Link to={target}>{rendered}</Link>;
  138. }
  139. return (
  140. <CellAction
  141. column={column}
  142. dataRow={data as TableDataRow}
  143. handleCellAction={(actions, value) => {
  144. updateQuery(query, actions, column, value);
  145. setUserQuery(query.formatString());
  146. }}
  147. allowActions={ALLOWED_CELL_ACTIONS}
  148. >
  149. {rendered}
  150. </CellAction>
  151. );
  152. }
  153. function getExploreFieldRenderer(
  154. field: string,
  155. meta: MetaType
  156. ): ReturnType<typeof getFieldRenderer> {
  157. if (field === 'id' || field === 'span_id') {
  158. return eventIdRenderFunc(field);
  159. }
  160. if (field === 'span.description') {
  161. return SpanDescriptionRenderer;
  162. }
  163. return getFieldRenderer(field, meta, false);
  164. }
  165. function eventIdRenderFunc(field: string) {
  166. function renderer(data: EventData) {
  167. const spanId: string | unknown = data?.[field];
  168. if (typeof spanId !== 'string') {
  169. return null;
  170. }
  171. return <Container>{getShortEventId(spanId)}</Container>;
  172. }
  173. return renderer;
  174. }
  175. function SpanDescriptionRenderer(data: EventData) {
  176. const {projects} = useProjects();
  177. const project = projects.find(p => p.slug === data.project);
  178. const value = data['span.description'];
  179. return (
  180. <span>
  181. <Tooltip
  182. title={value}
  183. containerDisplayMode="block"
  184. showOnlyOnOverflow
  185. maxWidth={400}
  186. >
  187. <Description>
  188. {project && (
  189. <ProjectBadge
  190. project={project ? project : {slug: data.project}}
  191. avatarSize={16}
  192. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  193. hideName
  194. />
  195. )}
  196. <WrappingText>
  197. {isUrl(value) ? (
  198. <ExternalLink href={value}>{value}</ExternalLink>
  199. ) : (
  200. nullableValue(value)
  201. )}
  202. </WrappingText>
  203. </Description>
  204. </Tooltip>
  205. </span>
  206. );
  207. }
  208. const StyledTimeSince = styled(TimeSince)`
  209. width: fit-content;
  210. `;
  211. const Description = styled('div')`
  212. ${p => p.theme.overflowEllipsis};
  213. display: flex;
  214. flex-direction: row;
  215. align-items: center;
  216. gap: ${space(1)};
  217. `;
  218. const WrappingText = styled('div')`
  219. ${p => p.theme.overflowEllipsis};
  220. width: auto;
  221. `;