fieldRenderer.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs';
  28. import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
  29. import {ALLOWED_CELL_ACTIONS} from '../components/table';
  30. interface FieldProps {
  31. column: TableColumn<keyof TableDataRow>;
  32. data: EventData;
  33. meta: MetaType;
  34. unit?: string;
  35. }
  36. export function FieldRenderer({data, meta, unit, column}: FieldProps) {
  37. const location = useLocation();
  38. const organization = useOrganization();
  39. const userQuery = useExploreQuery();
  40. const setUserQuery = useSetExploreQuery();
  41. const dateSelection = EventView.fromLocation(location).normalizeDateSelection(location);
  42. const query = new MutableSearch(userQuery);
  43. const field = column.name;
  44. const renderer = getExploreFieldRenderer(field, meta);
  45. let rendered = renderer(data, {
  46. location,
  47. organization,
  48. unit,
  49. });
  50. if (field === 'timestamp') {
  51. const date = new Date(data.timestamp);
  52. rendered = <StyledTimeSince unitStyle="extraShort" date={date} tooltipShowSeconds />;
  53. }
  54. if (field === 'trace') {
  55. const target = getTraceDetailsUrl({
  56. traceSlug: data.trace,
  57. timestamp: data.timestamp,
  58. organization,
  59. dateSelection,
  60. location,
  61. source: TraceViewSources.TRACES,
  62. });
  63. rendered = <Link to={target}>{rendered}</Link>;
  64. }
  65. if (['id', 'span_id', 'transaction.id'].includes(field)) {
  66. const spanId = field === 'transaction.id' ? undefined : data.span_id ?? data.id;
  67. const target = generateLinkToEventInTraceView({
  68. projectSlug: data.project,
  69. traceSlug: data.trace,
  70. timestamp: data.timestamp,
  71. targetId: data['transaction.span_id'],
  72. eventId: undefined,
  73. organization,
  74. location,
  75. spanId,
  76. source: TraceViewSources.TRACES,
  77. });
  78. rendered = <Link to={target}>{rendered}</Link>;
  79. }
  80. if (field === 'profile.id') {
  81. const target = generateProfileFlamechartRouteWithQuery({
  82. orgSlug: organization.slug,
  83. projectSlug: data.project,
  84. profileId: data['profile.id'],
  85. });
  86. rendered = <Link to={target}>{rendered}</Link>;
  87. }
  88. return (
  89. <CellAction
  90. column={column}
  91. dataRow={data as TableDataRow}
  92. handleCellAction={(actions, value) => {
  93. updateQuery(query, actions, column, value);
  94. setUserQuery(query.formatString());
  95. }}
  96. allowActions={ALLOWED_CELL_ACTIONS}
  97. >
  98. {rendered}
  99. </CellAction>
  100. );
  101. }
  102. function getExploreFieldRenderer(
  103. field: string,
  104. meta: MetaType
  105. ): ReturnType<typeof getFieldRenderer> {
  106. if (field === 'id' || field === 'span_id') {
  107. return eventIdRenderFunc(field);
  108. }
  109. if (field === 'span.description') {
  110. return SpanDescriptionRenderer;
  111. }
  112. return getFieldRenderer(field, meta, false);
  113. }
  114. function eventIdRenderFunc(field: string) {
  115. function renderer(data: EventData) {
  116. const spanId: string | unknown = data?.[field];
  117. if (typeof spanId !== 'string') {
  118. return null;
  119. }
  120. return <Container>{getShortEventId(spanId)}</Container>;
  121. }
  122. return renderer;
  123. }
  124. function SpanDescriptionRenderer(data: EventData) {
  125. const {projects} = useProjects();
  126. const project = projects.find(p => p.slug === data.project);
  127. const value = data['span.description'];
  128. return (
  129. <span>
  130. <Tooltip
  131. title={value}
  132. containerDisplayMode="block"
  133. showOnlyOnOverflow
  134. maxWidth={400}
  135. >
  136. <Description>
  137. {project && (
  138. <ProjectBadge
  139. project={project ? project : {slug: data.project}}
  140. avatarSize={16}
  141. hideName
  142. />
  143. )}
  144. <WrappingText>
  145. {isUrl(value) ? (
  146. <ExternalLink href={value}>{value}</ExternalLink>
  147. ) : (
  148. nullableValue(value)
  149. )}
  150. </WrappingText>
  151. </Description>
  152. </Tooltip>
  153. </span>
  154. );
  155. }
  156. const StyledTimeSince = styled(TimeSince)`
  157. width: fit-content;
  158. `;
  159. const Description = styled('div')`
  160. ${p => p.theme.overflowEllipsis};
  161. display: flex;
  162. flex-direction: row;
  163. align-items: center;
  164. gap: ${space(1)};
  165. `;
  166. const WrappingText = styled('div')`
  167. ${p => p.theme.overflowEllipsis};
  168. width: auto;
  169. `;