fieldRenderers.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import {type Theme, useTheme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {LinkButton} from 'sentry/components/button';
  4. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  5. import Link from 'sentry/components/links/link';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import {ROW_HEIGHT, ROW_PADDING} from 'sentry/components/performance/waterfall/constants';
  8. import {RowRectangle} from 'sentry/components/performance/waterfall/rowBar';
  9. import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
  10. import PerformanceDuration from 'sentry/components/performanceDuration';
  11. import {Tooltip} from 'sentry/components/tooltip';
  12. import {IconIssues} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import type {DateString} from 'sentry/types';
  15. import EventView from 'sentry/utils/discover/eventView';
  16. import {
  17. generateEventSlug,
  18. generateLinkToEventInTraceView,
  19. } from 'sentry/utils/discover/urls';
  20. import {getShortEventId} from 'sentry/utils/events';
  21. import toPercent from 'sentry/utils/number/toPercent';
  22. import Projects from 'sentry/utils/projects';
  23. import {useLocation} from 'sentry/utils/useLocation';
  24. import useOrganization from 'sentry/utils/useOrganization';
  25. import usePageFilters from 'sentry/utils/usePageFilters';
  26. import useProjects from 'sentry/utils/useProjects';
  27. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  28. import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
  29. import {transactionSummaryRouteWithQuery} from 'sentry/views/performance/transactionSummary/utils';
  30. import {useTraceMeta} from '../newTraceDetails/traceApi/useTraceMeta';
  31. import type {TraceResult} from './content';
  32. import type {Field} from './data';
  33. interface ProjectRendererProps {
  34. projectSlug: string;
  35. hideName?: boolean;
  36. }
  37. export function ProjectRenderer({projectSlug, hideName}: ProjectRendererProps) {
  38. const organization = useOrganization();
  39. return (
  40. <Projects orgId={organization.slug} slugs={[projectSlug]}>
  41. {({projects}) => {
  42. const project = projects.find(p => p.slug === projectSlug);
  43. return (
  44. <ProjectBadge
  45. hideName={hideName}
  46. project={project ? project : {slug: projectSlug}}
  47. avatarSize={16}
  48. />
  49. );
  50. }}
  51. </Projects>
  52. );
  53. }
  54. export const TraceBreakdownContainer = styled('div')`
  55. position: relative;
  56. display: flex;
  57. min-width: 150px;
  58. height: ${ROW_HEIGHT - 2 * ROW_PADDING}px;
  59. background-color: ${p => p.theme.gray100};
  60. `;
  61. const RectangleTraceBreakdown = styled(RowRectangle)`
  62. position: relative;
  63. width: 100%;
  64. `;
  65. export function TraceBreakdownRenderer({trace}: {trace: TraceResult<Field>}) {
  66. const theme = useTheme();
  67. return (
  68. <TraceBreakdownContainer data-test-id="relative-ops-breakdown">
  69. {trace.breakdowns.map(breakdown => {
  70. return (
  71. <SpanBreakdownSliceRenderer
  72. key={breakdown.start + (breakdown.project ?? t('missing instrumentation'))}
  73. sliceName={breakdown.project}
  74. sliceStart={breakdown.start}
  75. sliceEnd={breakdown.end}
  76. trace={trace}
  77. theme={theme}
  78. />
  79. );
  80. })}
  81. </TraceBreakdownContainer>
  82. );
  83. }
  84. export function SpanBreakdownSliceRenderer({
  85. trace,
  86. theme,
  87. sliceName,
  88. sliceStart,
  89. sliceEnd,
  90. }: {
  91. sliceEnd: number;
  92. sliceName: string | null;
  93. sliceStart: number;
  94. theme: Theme;
  95. trace: TraceResult<Field>;
  96. }) {
  97. const traceDuration = trace.end - trace.start;
  98. const sliceDuration = sliceEnd - sliceStart;
  99. if (sliceDuration <= 0) {
  100. return null;
  101. }
  102. const sliceColor = sliceName ? pickBarColor(sliceName) : theme.gray100;
  103. const slicePercent = toPercent(sliceDuration / traceDuration);
  104. const relativeSliceStart = sliceStart - trace.start;
  105. const sliceOffset = toPercent(relativeSliceStart / traceDuration);
  106. return (
  107. <div style={{width: slicePercent, left: sliceOffset, position: 'absolute'}}>
  108. <Tooltip
  109. title={
  110. <div>
  111. <div>{sliceName}</div>
  112. <div>
  113. <PerformanceDuration milliseconds={sliceDuration} abbreviation />
  114. </div>
  115. </div>
  116. }
  117. containerDisplayMode="block"
  118. >
  119. <RectangleTraceBreakdown
  120. style={{
  121. backgroundColor: sliceColor,
  122. }}
  123. onClick={_ => {}}
  124. />
  125. </Tooltip>
  126. </div>
  127. );
  128. }
  129. interface SpanIdRendererProps {
  130. projectSlug: string;
  131. spanId: string;
  132. timestamp: string;
  133. traceId: string;
  134. transactionId: string;
  135. }
  136. export function SpanIdRenderer({
  137. projectSlug,
  138. spanId,
  139. timestamp,
  140. traceId,
  141. transactionId,
  142. }: SpanIdRendererProps) {
  143. const location = useLocation();
  144. const organization = useOrganization();
  145. const target = generateLinkToEventInTraceView({
  146. eventSlug: generateEventSlug({
  147. id: transactionId,
  148. project: projectSlug,
  149. }),
  150. organization,
  151. location,
  152. eventView: EventView.fromLocation(location),
  153. dataRow: {
  154. id: transactionId,
  155. trace: traceId,
  156. timestamp,
  157. },
  158. spanId,
  159. });
  160. return <Link to={target}>{getShortEventId(spanId)}</Link>;
  161. }
  162. interface TraceIdRendererProps {
  163. traceId: string;
  164. timestamp?: DateString;
  165. transactionId?: string;
  166. }
  167. export function TraceIdRenderer({
  168. traceId,
  169. timestamp,
  170. transactionId,
  171. }: TraceIdRendererProps) {
  172. const organization = useOrganization();
  173. const {selection} = usePageFilters();
  174. const stringOrNumberTimestamp =
  175. timestamp instanceof Date ? timestamp.toISOString() : timestamp ?? '';
  176. const target = getTraceDetailsUrl(
  177. organization,
  178. traceId,
  179. {
  180. start: selection.datetime.start,
  181. end: selection.datetime.end,
  182. statsPeriod: selection.datetime.period,
  183. },
  184. {},
  185. stringOrNumberTimestamp,
  186. transactionId
  187. );
  188. return <Link to={target}>{getShortEventId(traceId)}</Link>;
  189. }
  190. interface TransactionRendererProps {
  191. projectSlug: string;
  192. transaction: string;
  193. }
  194. export function TransactionRenderer({
  195. projectSlug,
  196. transaction,
  197. }: TransactionRendererProps) {
  198. const location = useLocation();
  199. const organization = useOrganization();
  200. const {projects} = useProjects({slugs: [projectSlug]});
  201. const target = transactionSummaryRouteWithQuery({
  202. orgSlug: organization.slug,
  203. transaction,
  204. query: {
  205. ...location.query,
  206. query: undefined,
  207. },
  208. projectID: String(projects[0]?.id ?? ''),
  209. });
  210. return <Link to={target}>{transaction}</Link>;
  211. }
  212. export function TraceIssuesRenderer({trace}: {trace: TraceResult<Field>}) {
  213. const traceMeta = useTraceMeta(trace.trace);
  214. const organization = useOrganization();
  215. const issueCount = !traceMeta.data
  216. ? undefined
  217. : traceMeta.data.errors + traceMeta.data.performance_issues;
  218. return (
  219. <LinkButton
  220. to={normalizeUrl({
  221. pathname: `/organizations/${organization.slug}/issues`,
  222. query: {
  223. query: `is:unresolved trace:"${trace.trace}"`,
  224. },
  225. })}
  226. size="xs"
  227. icon={<IconIssues size="xs" />}
  228. >
  229. {issueCount !== undefined ? (
  230. issueCount
  231. ) : (
  232. <LoadingIndicator
  233. size={12}
  234. mini
  235. style={{height: '12px', width: '12px', margin: 0, marginRight: 0}}
  236. />
  237. )}
  238. {issueCount === 100 && '+'}
  239. </LinkButton>
  240. );
  241. }