missingInstrumentation.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 hasNewTraceUi={hasTraceNewUi}>
  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. <TextBlock>
  54. {tct(
  55. '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].',
  56. {
  57. customInstrumentationLink: (
  58. <ExternalLink href={getCustomInstrumentationLink(project)} />
  59. ),
  60. }
  61. )}
  62. </TextBlock>
  63. {event?.projectSlug ? (
  64. <ProfilesProvider
  65. orgSlug={organization.slug}
  66. projectSlug={node.event?.projectSlug ?? ''}
  67. profileId={profileId || ''}
  68. >
  69. <ProfileContext.Consumer>
  70. {profiles => (
  71. <ProfileGroupProvider
  72. type="flamechart"
  73. input={profiles?.type === 'resolved' ? profiles.data : null}
  74. traceID={profileId || ''}
  75. >
  76. <ProfilePreview event={event!} node={node} />
  77. </ProfileGroupProvider>
  78. )}
  79. </ProfileContext.Consumer>
  80. </ProfilesProvider>
  81. ) : null}
  82. <TextBlock>
  83. {t(
  84. "You can turn off the 'No Instrumentation' feature using the settings dropdown above."
  85. )}
  86. </TextBlock>
  87. </TraceDrawerComponents.DetailContainer>
  88. );
  89. }
  90. const TextBlock = styled('div')`
  91. font-size: ${p => p.theme.fontSizeLarge};
  92. line-height: 1.5;
  93. margin-bottom: ${space(2)};
  94. `;
  95. function LegacyMissingInstrumentationNodeDetails({
  96. node,
  97. onParentClick,
  98. onTabScrollToNode,
  99. organization,
  100. }: TraceTreeNodeDetailsProps<MissingInstrumentationNode>) {
  101. const theme = useTheme();
  102. const {projects} = useProjects();
  103. const parentTransaction = TraceTree.ParentTransaction(node);
  104. const event = node.previous.event ?? node.next.event ?? null;
  105. const project = projects.find(proj => proj.slug === event?.projectSlug);
  106. const profileId = event?.contexts?.profile?.profile_id ?? null;
  107. const items: SectionCardKeyValueList = [
  108. {
  109. key: 'duration',
  110. subject: t('Duration'),
  111. value: getDuration(node.value.timestamp - node.value.start_timestamp, 2, true),
  112. },
  113. {
  114. key: 'previous_span',
  115. subject: t('Previous Span'),
  116. value: `${node.previous.value.op} - ${node.previous.value.description}`,
  117. },
  118. {
  119. key: 'next_span',
  120. subject: t('Next Span'),
  121. value: `${node.next.value.op} - ${node.next.value.description}`,
  122. },
  123. ];
  124. if (profileId && project?.slug) {
  125. items.push({
  126. key: 'profile_id',
  127. subject: 'Profile ID',
  128. value: (
  129. <TraceDrawerComponents.CopyableCardValueWithLink
  130. value={profileId}
  131. linkTarget={generateProfileFlamechartRouteWithQuery({
  132. orgSlug: organization.slug,
  133. projectSlug: project.slug,
  134. profileId,
  135. })}
  136. linkText={t('View Profile')}
  137. />
  138. ),
  139. });
  140. }
  141. if (parentTransaction) {
  142. items.push({
  143. key: 'parent_transaction',
  144. subject: t('Parent Transaction'),
  145. value: (
  146. <a onClick={() => onParentClick(parentTransaction)}>
  147. {getTraceTabTitle(parentTransaction)}
  148. </a>
  149. ),
  150. });
  151. }
  152. return (
  153. <TraceDrawerComponents.DetailContainer>
  154. <TraceDrawerComponents.LegacyHeaderContainer>
  155. <TraceDrawerComponents.Title>
  156. <TraceDrawerComponents.IconTitleWrapper>
  157. <TraceDrawerComponents.IconBorder
  158. backgroundColor={makeTraceNodeBarColor(theme, node)}
  159. >
  160. <IconSpan size="md" />
  161. </TraceDrawerComponents.IconBorder>
  162. <div style={{fontWeight: 'bold'}}>{t('Missing Instrumentation')}</div>
  163. </TraceDrawerComponents.IconTitleWrapper>
  164. </TraceDrawerComponents.Title>
  165. <TraceDrawerComponents.NodeActions
  166. organization={organization}
  167. node={node}
  168. onTabScrollToNode={onTabScrollToNode}
  169. />
  170. </TraceDrawerComponents.LegacyHeaderContainer>
  171. {node.event?.projectSlug ? (
  172. <ProfilesProvider
  173. orgSlug={organization.slug}
  174. projectSlug={node.event?.projectSlug ?? ''}
  175. profileId={profileId || ''}
  176. >
  177. <ProfileContext.Consumer>
  178. {profiles => (
  179. <ProfileGroupProvider
  180. type="flamechart"
  181. input={profiles?.type === 'resolved' ? profiles.data : null}
  182. traceID={profileId || ''}
  183. >
  184. <ProfilePreview event={node.event!} node={node} />
  185. </ProfileGroupProvider>
  186. )}
  187. </ProfileContext.Consumer>
  188. </ProfilesProvider>
  189. ) : null}
  190. <TraceDrawerComponents.SectionCard items={items} title={t('General')} />
  191. </TraceDrawerComponents.DetailContainer>
  192. );
  193. }