missingInstrumentation.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import {useTheme} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import ExternalLink from 'sentry/components/links/externalLink';
  4. import {IconSpan} from 'sentry/icons';
  5. import {t, tct} from 'sentry/locale';
  6. import {space} from 'sentry/styles/space';
  7. import getDuration from 'sentry/utils/duration/getDuration';
  8. import {generateProfileFlamechartRouteWithQuery} from 'sentry/utils/profiling/routes';
  9. import useProjects from 'sentry/utils/useProjects';
  10. import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider';
  11. import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider';
  12. import {getCustomInstrumentationLink} from '../../traceConfigurations';
  13. import {ProfilePreview} from '../../traceDrawer/details/profiling/profilePreview';
  14. import type {TraceTreeNodeDetailsProps} from '../../traceDrawer/tabs/traceTreeNodeDetails';
  15. import type {MissingInstrumentationNode} from '../../traceModels/missingInstrumentationNode';
  16. import {TraceTree} from '../../traceModels/traceTree';
  17. import {makeTraceNodeBarColor} from '../../traceRow/traceBar';
  18. import {getTraceTabTitle} from '../../traceState/traceTabs';
  19. import {useHasTraceNewUi} from '../../useHasTraceNewUi';
  20. import {type SectionCardKeyValueList, TraceDrawerComponents} from './styles';
  21. export function MissingInstrumentationNodeDetails(
  22. props: TraceTreeNodeDetailsProps<MissingInstrumentationNode>
  23. ) {
  24. const {projects} = useProjects();
  25. const hasTraceNewUi = useHasTraceNewUi();
  26. if (!hasTraceNewUi) {
  27. return <LegacyMissingInstrumentationNodeDetails {...props} />;
  28. }
  29. const {node, organization, onTabScrollToNode} = props;
  30. const event = node.previous.event ?? node.next.event ?? null;
  31. const project = projects.find(proj => proj.slug === event?.projectSlug);
  32. const profileId = event?.contexts?.profile?.profile_id ?? null;
  33. return (
  34. <TraceDrawerComponents.DetailContainer>
  35. <TraceDrawerComponents.HeaderContainer>
  36. <TraceDrawerComponents.Title>
  37. <TraceDrawerComponents.LegacyTitleText>
  38. <TraceDrawerComponents.TitleText>
  39. {t('No Instrumentation')}
  40. </TraceDrawerComponents.TitleText>
  41. <TraceDrawerComponents.SubtitleWithCopyButton
  42. hideCopyButton
  43. text={t('How Awkward')}
  44. />
  45. </TraceDrawerComponents.LegacyTitleText>
  46. </TraceDrawerComponents.Title>
  47. <TraceDrawerComponents.NodeActions
  48. node={node}
  49. organization={organization}
  50. onTabScrollToNode={onTabScrollToNode}
  51. />
  52. </TraceDrawerComponents.HeaderContainer>
  53. <TraceDrawerComponents.BodyContainer hasNewTraceUi={hasTraceNewUi}>
  54. <TextBlock>
  55. {tct(
  56. 'It looks like there’s more than 100ms unaccounted for. This might be a missing service or just idle time. If you know there’s something going on, you can [customInstrumentationLink: add more spans using custom instrumentation].',
  57. {
  58. customInstrumentationLink: (
  59. <ExternalLink href={getCustomInstrumentationLink(project)} />
  60. ),
  61. }
  62. )}
  63. </TextBlock>
  64. {event?.projectSlug ? (
  65. <ProfilesProvider
  66. orgSlug={organization.slug}
  67. projectSlug={event?.projectSlug ?? ''}
  68. profileId={profileId || ''}
  69. >
  70. <ProfileContext.Consumer>
  71. {profiles => (
  72. <ProfileGroupProvider
  73. type="flamechart"
  74. input={profiles?.type === 'resolved' ? profiles.data : null}
  75. traceID={profileId || ''}
  76. >
  77. <ProfilePreview event={event!} node={node} />
  78. </ProfileGroupProvider>
  79. )}
  80. </ProfileContext.Consumer>
  81. </ProfilesProvider>
  82. ) : null}
  83. <TextBlock>
  84. {t(
  85. "You can turn off the 'No Instrumentation' feature using the settings dropdown above."
  86. )}
  87. </TextBlock>
  88. </TraceDrawerComponents.BodyContainer>
  89. </TraceDrawerComponents.DetailContainer>
  90. );
  91. }
  92. const TextBlock = styled('div')`
  93. font-size: ${p => p.theme.fontSizeLarge};
  94. line-height: 1.5;
  95. margin-bottom: ${space(2)};
  96. `;
  97. function LegacyMissingInstrumentationNodeDetails({
  98. node,
  99. onParentClick,
  100. onTabScrollToNode,
  101. organization,
  102. }: TraceTreeNodeDetailsProps<MissingInstrumentationNode>) {
  103. const theme = useTheme();
  104. const {projects} = useProjects();
  105. const parentTransaction = TraceTree.ParentTransaction(node);
  106. const event = node.previous.event ?? node.next.event ?? null;
  107. const project = projects.find(proj => proj.slug === event?.projectSlug);
  108. const profileId = event?.contexts?.profile?.profile_id ?? null;
  109. const items: SectionCardKeyValueList = [
  110. {
  111. key: 'duration',
  112. subject: t('Duration'),
  113. value: getDuration(node.value.timestamp - node.value.start_timestamp, 2, true),
  114. },
  115. {
  116. key: 'previous_span',
  117. subject: t('Previous Span'),
  118. value: `${node.previous.value.op} - ${node.previous.value.description}`,
  119. },
  120. {
  121. key: 'next_span',
  122. subject: t('Next Span'),
  123. value: `${node.next.value.op} - ${node.next.value.description}`,
  124. },
  125. ];
  126. if (profileId && project?.slug) {
  127. items.push({
  128. key: 'profile_id',
  129. subject: 'Profile ID',
  130. value: (
  131. <TraceDrawerComponents.CopyableCardValueWithLink
  132. value={profileId}
  133. linkTarget={generateProfileFlamechartRouteWithQuery({
  134. orgSlug: organization.slug,
  135. projectSlug: project.slug,
  136. profileId,
  137. })}
  138. linkText={t('View Profile')}
  139. />
  140. ),
  141. });
  142. }
  143. if (parentTransaction) {
  144. items.push({
  145. key: 'parent_transaction',
  146. subject: t('Parent Transaction'),
  147. value: (
  148. <a onClick={() => onParentClick(parentTransaction)}>
  149. {getTraceTabTitle(parentTransaction)}
  150. </a>
  151. ),
  152. });
  153. }
  154. return (
  155. <TraceDrawerComponents.DetailContainer>
  156. <TraceDrawerComponents.LegacyHeaderContainer>
  157. <TraceDrawerComponents.Title>
  158. <TraceDrawerComponents.IconTitleWrapper>
  159. <TraceDrawerComponents.IconBorder
  160. backgroundColor={makeTraceNodeBarColor(theme, node)}
  161. >
  162. <IconSpan size="md" />
  163. </TraceDrawerComponents.IconBorder>
  164. <div style={{fontWeight: 'bold'}}>{t('Missing Instrumentation')}</div>
  165. </TraceDrawerComponents.IconTitleWrapper>
  166. </TraceDrawerComponents.Title>
  167. <TraceDrawerComponents.NodeActions
  168. organization={organization}
  169. node={node}
  170. onTabScrollToNode={onTabScrollToNode}
  171. />
  172. </TraceDrawerComponents.LegacyHeaderContainer>
  173. <TraceDrawerComponents.BodyContainer>
  174. {node.event?.projectSlug ? (
  175. <ProfilesProvider
  176. orgSlug={organization.slug}
  177. projectSlug={node.event?.projectSlug ?? ''}
  178. profileId={profileId || ''}
  179. >
  180. <ProfileContext.Consumer>
  181. {profiles => (
  182. <ProfileGroupProvider
  183. type="flamechart"
  184. input={profiles?.type === 'resolved' ? profiles.data : null}
  185. traceID={profileId || ''}
  186. >
  187. <ProfilePreview event={node.event!} node={node} />
  188. </ProfileGroupProvider>
  189. )}
  190. </ProfileContext.Consumer>
  191. </ProfilesProvider>
  192. ) : null}
  193. <TraceDrawerComponents.SectionCard items={items} title={t('General')} />
  194. </TraceDrawerComponents.BodyContainer>
  195. </TraceDrawerComponents.DetailContainer>
  196. );
  197. }