fieldRenderers.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import type {FC, ReactText} from 'react';
  2. import type {GridColumnOrder} from 'sentry/components/gridEditable';
  3. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  4. import Link from 'sentry/components/links/link';
  5. import PerformanceDuration from 'sentry/components/performanceDuration';
  6. import type {DateString} from 'sentry/types';
  7. import {defined} from 'sentry/utils';
  8. import {Container, FieldDateTime} from 'sentry/utils/discover/styles';
  9. import {getShortEventId} from 'sentry/utils/events';
  10. import {getTransactionDetailsUrl} from 'sentry/utils/performance/urls';
  11. import Projects from 'sentry/utils/projects';
  12. import {decodeScalar} from 'sentry/utils/queryString';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. import usePageFilters from 'sentry/utils/usePageFilters';
  16. import useProjects from 'sentry/utils/useProjects';
  17. import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
  18. import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
  19. import type {ColumnKey, DataRow} from './types';
  20. interface FieldRendererProps {
  21. column: GridColumnOrder<ColumnKey>;
  22. row: DataRow;
  23. }
  24. export function getFieldRenderer(field: ColumnKey): FC<FieldRendererProps> {
  25. return fieldRenderers[field] ?? DefaultRenderer;
  26. }
  27. const fieldRenderers: Record<ReactText, FC<FieldRendererProps>> = {
  28. project: ProjectRenderer,
  29. span_id: SpanIdRenderer,
  30. 'span.duration': SpanDurationRenderer,
  31. 'span.self_time': SpanSelfTimeRenderer,
  32. timestamp: TimestampRenderer,
  33. trace: TraceIdRenderer,
  34. transaction: TransactionRenderer,
  35. 'transaction.id': TransactionIdRenderer,
  36. };
  37. function DefaultRenderer({row, column}: FieldRendererProps) {
  38. // TODO: this can be smarter based on the type of the value
  39. return <Container>{row[column.key]}</Container>;
  40. }
  41. function ProjectRenderer(props: FieldRendererProps) {
  42. const projectSlug = props.row.project;
  43. if (!defined(projectSlug)) {
  44. return <DefaultRenderer {...props} />;
  45. }
  46. return <_ProjectRenderer {...props} projectSlug={projectSlug} />;
  47. }
  48. interface ProjectRendererProps extends FieldRendererProps {
  49. projectSlug: string;
  50. }
  51. function _ProjectRenderer({projectSlug}: ProjectRendererProps) {
  52. const organization = useOrganization();
  53. return (
  54. <Container>
  55. <Projects orgId={organization.slug} slugs={[projectSlug]}>
  56. {({projects}) => {
  57. const project = projects.find(p => p.slug === projectSlug);
  58. return (
  59. <ProjectBadge
  60. project={project ? project : {slug: projectSlug}}
  61. avatarSize={16}
  62. />
  63. );
  64. }}
  65. </Projects>
  66. </Container>
  67. );
  68. }
  69. function SpanIdRenderer(props: FieldRendererProps) {
  70. const projectSlug = props.row.project;
  71. const spanId = props.row.span_id;
  72. const transactionId = props.row['transaction.id'];
  73. if (!defined(projectSlug) || !defined(spanId) || !defined(transactionId)) {
  74. return <DefaultRenderer {...props} />;
  75. }
  76. return (
  77. <_SpanIdRenderer
  78. {...props}
  79. projectSlug={projectSlug}
  80. spanId={spanId}
  81. transactionId={transactionId}
  82. />
  83. );
  84. }
  85. interface _SpanIdRendererProps extends FieldRendererProps {
  86. projectSlug: string;
  87. spanId: string;
  88. transactionId: string;
  89. }
  90. function _SpanIdRenderer({projectSlug, spanId, transactionId}: _SpanIdRendererProps) {
  91. const organization = useOrganization();
  92. const target = getTransactionDetailsUrl(
  93. organization.slug,
  94. `${projectSlug}:${transactionId}`,
  95. undefined,
  96. undefined,
  97. spanId
  98. );
  99. return <Link to={target}>{getShortEventId(spanId)}</Link>;
  100. }
  101. function TraceIdRenderer(props: FieldRendererProps) {
  102. const traceId = props.row.trace;
  103. if (!defined(traceId)) {
  104. return <DefaultRenderer {...props} />;
  105. }
  106. return (
  107. <_TraceIdRenderer
  108. {...props}
  109. traceId={traceId}
  110. transactionId={props.row['transaction.id'] ?? undefined}
  111. timestamp={props.row.timestamp}
  112. />
  113. );
  114. }
  115. interface TraceIdRendererProps extends FieldRendererProps {
  116. traceId: string;
  117. timestamp?: DateString;
  118. transactionId?: string;
  119. }
  120. function _TraceIdRenderer({traceId, timestamp, transactionId}: TraceIdRendererProps) {
  121. const organization = useOrganization();
  122. const {selection} = usePageFilters();
  123. const stringOrNumberTimestamp =
  124. timestamp instanceof Date ? timestamp.toISOString() : timestamp ?? '';
  125. const target = getTraceDetailsUrl(
  126. organization,
  127. traceId,
  128. {
  129. start: selection.datetime.start,
  130. end: selection.datetime.end,
  131. statsPeriod: selection.datetime.period,
  132. },
  133. {},
  134. stringOrNumberTimestamp,
  135. transactionId
  136. );
  137. return (
  138. <Container>
  139. <Link to={target}>{getShortEventId(traceId)}</Link>
  140. </Container>
  141. );
  142. }
  143. function TransactionIdRenderer(props: FieldRendererProps) {
  144. const projectSlug = props.row.project;
  145. const transactionId = props.row['transaction.id'];
  146. if (!defined(projectSlug) || !defined(transactionId)) {
  147. return <DefaultRenderer {...props} />;
  148. }
  149. return (
  150. <_TransactionIdRenderer
  151. {...props}
  152. projectSlug={projectSlug}
  153. transactionId={transactionId}
  154. />
  155. );
  156. }
  157. interface TransactionIdRendererProps extends FieldRendererProps {
  158. projectSlug: string;
  159. transactionId: string;
  160. }
  161. function _TransactionIdRenderer({
  162. projectSlug,
  163. transactionId,
  164. }: TransactionIdRendererProps) {
  165. const organization = useOrganization();
  166. const target = getTransactionDetailsUrl(
  167. organization.slug,
  168. `${projectSlug}:${transactionId}`,
  169. undefined,
  170. undefined
  171. );
  172. return <Link to={target}>{getShortEventId(transactionId)}</Link>;
  173. }
  174. function TransactionRenderer(props: FieldRendererProps) {
  175. const projectSlug = props.row.project;
  176. const transaction = props.row.transaction;
  177. if (!defined(projectSlug) || !defined(transaction)) {
  178. return <DefaultRenderer {...props} />;
  179. }
  180. return (
  181. <_TransactionRenderer
  182. {...props}
  183. projectSlug={projectSlug}
  184. transaction={transaction}
  185. />
  186. );
  187. }
  188. interface TransactionRendererProps {
  189. projectSlug: string;
  190. transaction: string;
  191. }
  192. function _TransactionRenderer({projectSlug, transaction}: TransactionRendererProps) {
  193. const location = useLocation();
  194. const organization = useOrganization();
  195. const {projects} = useProjects({slugs: [projectSlug]});
  196. const target = transactionSummaryRouteWithQuery({
  197. orgSlug: organization.slug,
  198. transaction,
  199. query: {
  200. ...location.query,
  201. query: undefined,
  202. },
  203. projectID: String(projects[0]?.id ?? ''),
  204. });
  205. return (
  206. <Container>
  207. <Link to={target}>{transaction}</Link>
  208. </Container>
  209. );
  210. }
  211. function TimestampRenderer(props: FieldRendererProps) {
  212. const location = useLocation();
  213. const timestamp = props.row.timestamp;
  214. if (!defined(timestamp)) {
  215. return <DefaultRenderer {...props} />;
  216. }
  217. const utc = decodeScalar(location?.query?.utc) === 'true';
  218. return <FieldDateTime date={timestamp} year seconds timeZone utc={utc} />;
  219. }
  220. function SpanDurationRenderer(props: FieldRendererProps) {
  221. const duration = props.row['span.duration'];
  222. if (!defined(duration)) {
  223. return <DefaultRenderer {...props} />;
  224. }
  225. return <PerformanceDuration milliseconds={duration} abbreviation />;
  226. }
  227. function SpanSelfTimeRenderer(props: FieldRendererProps) {
  228. const duration = props.row['span.duration'];
  229. if (!defined(duration)) {
  230. return <DefaultRenderer {...props} />;
  231. }
  232. return <PerformanceDuration milliseconds={duration} abbreviation />;
  233. }