traceConfigurations.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import {type ReactNode, useMemo} from 'react';
  2. import {ClassNames} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {Button, LinkButton} from 'sentry/components/button';
  5. import {Hovercard} from 'sentry/components/hovercard';
  6. import {platformsWithNestedInstrumentationGuides} from 'sentry/data/platformCategories';
  7. import {IconOpen, IconQuestion} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {EventTransaction} from 'sentry/types/event';
  11. import type {Project} from 'sentry/types/project';
  12. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  13. import type RequestError from 'sentry/utils/requestError/requestError';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. import useProjects from 'sentry/utils/useProjects';
  16. import {traceAnalytics} from 'sentry/views/performance/newTraceDetails/traceAnalytics';
  17. function Resource({
  18. title,
  19. subtitle,
  20. link,
  21. }: {
  22. link: string;
  23. subtitle: ReactNode;
  24. title: string;
  25. }) {
  26. const organization = useOrganization();
  27. return (
  28. <StyledLinkButton
  29. icon={<IconOpen />}
  30. borderless
  31. external
  32. href={link}
  33. onClick={() => {
  34. traceAnalytics.trackTraceConfigurationsDocsClicked(organization, title);
  35. }}
  36. >
  37. <ButtonContent>
  38. <ButtonTitle>{title}</ButtonTitle>
  39. <ButtonSubtitle>{subtitle}</ButtonSubtitle>
  40. </ButtonContent>
  41. </StyledLinkButton>
  42. );
  43. }
  44. type ParsedPlatform = {
  45. platformName: string;
  46. framework?: string;
  47. };
  48. function parsePlatform(platform: string): ParsedPlatform {
  49. // Except react-native, all other project platforms have the following two structures:
  50. // 1. "{language}-{framework}", e.g. "javascript-nextjs"
  51. // 2. "{language}", e.g. "python"
  52. const [platformName, framework] =
  53. platform === 'react-native' ? ['react-native', undefined] : platform.split('-');
  54. return {platformName, framework};
  55. }
  56. export function getCustomInstrumentationLink(project: Project | undefined): string {
  57. // Default to JavaScript guide if project or platform is not available
  58. if (!project || !project.platform) {
  59. return `https://docs.sentry.io/platforms/javascript/tracing/instrumentation/custom-instrumentation/`;
  60. }
  61. const {platformName, framework} = parsePlatform(project.platform);
  62. return platformsWithNestedInstrumentationGuides.includes(project.platform) && framework
  63. ? `https://docs.sentry.io/platforms/${platformName}/guides/${framework}/tracing/instrumentation/custom-instrumentation/`
  64. : `https://docs.sentry.io/platforms/${platformName}/tracing/instrumentation/custom-instrumentation/`;
  65. }
  66. function getDistributedTracingLink(project: Project | undefined): string {
  67. // Default to JavaScript guide if project or platform is not available
  68. if (!project || !project.platform) {
  69. return `https://docs.sentry.io/platforms/javascript/tracing/trace-propagation/`;
  70. }
  71. const {platformName, framework} = parsePlatform(project.platform);
  72. return framework
  73. ? `https://docs.sentry.io/platforms/${platformName}/guides/${framework}/tracing/trace-propagation/`
  74. : `https://docs.sentry.io/platforms/${platformName}/tracing/trace-propagation/`;
  75. }
  76. type ResourceButtonsProps = {
  77. customInstrumentationLink: string;
  78. distributedTracingLink: string;
  79. };
  80. function ResourceButtons({
  81. customInstrumentationLink,
  82. distributedTracingLink,
  83. }: ResourceButtonsProps) {
  84. return (
  85. <ButtonContainer>
  86. <Resource
  87. title={t('Custom Instrumentation')}
  88. subtitle={t('Add Custom Spans or Transactions to your traces')}
  89. link={customInstrumentationLink}
  90. />
  91. <Resource
  92. title={t('Distributed Tracing')}
  93. subtitle={t('See the whole trace across all your services')}
  94. link={distributedTracingLink}
  95. />
  96. </ButtonContainer>
  97. );
  98. }
  99. type TraceConfigurationsProps = {
  100. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  101. };
  102. export default function TraceConfigurations({
  103. rootEventResults,
  104. }: TraceConfigurationsProps) {
  105. const {projects} = useProjects();
  106. const traceProject = useMemo(() => {
  107. return rootEventResults.data
  108. ? projects.find(p => p.id === rootEventResults.data.projectID)
  109. : undefined;
  110. }, [projects, rootEventResults.data]);
  111. const customInstrumentationLink = useMemo(
  112. () => getCustomInstrumentationLink(traceProject),
  113. [traceProject]
  114. );
  115. const distributedTracingLink = useMemo(
  116. () => getDistributedTracingLink(traceProject),
  117. [traceProject]
  118. );
  119. return (
  120. <ClassNames>
  121. {({css}) => (
  122. <Hovercard
  123. body={
  124. <ResourceButtons
  125. customInstrumentationLink={customInstrumentationLink}
  126. distributedTracingLink={distributedTracingLink}
  127. />
  128. }
  129. bodyClassName={css`
  130. padding: ${space(1)};
  131. `}
  132. position="top-end"
  133. >
  134. <Button
  135. size="sm"
  136. icon={<IconQuestion />}
  137. aria-label={t('trace configure resources')}
  138. >
  139. {t('Configure Traces')}
  140. </Button>
  141. </Hovercard>
  142. )}
  143. </ClassNames>
  144. );
  145. }
  146. const ButtonContainer = styled('div')`
  147. display: flex;
  148. flex-direction: column;
  149. gap: ${space(1)};
  150. align-items: flex-start;
  151. `;
  152. const ButtonContent = styled('div')`
  153. display: flex;
  154. flex-direction: column;
  155. text-align: left;
  156. white-space: pre-line;
  157. gap: ${space(0.25)};
  158. `;
  159. const ButtonTitle = styled('div')`
  160. font-weight: ${p => p.theme.fontWeightNormal};
  161. `;
  162. const ButtonSubtitle = styled('div')`
  163. color: ${p => p.theme.gray300};
  164. font-weight: ${p => p.theme.fontWeightNormal};
  165. font-size: ${p => p.theme.fontSizeSmall};
  166. `;
  167. const StyledLinkButton = styled(LinkButton)`
  168. padding: ${space(1)};
  169. height: auto;
  170. `;