traceConfigurations.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 './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. const platformParts = platform.split('-');
  50. // For example: dotnet-google-cloud-functions, we want to split it into
  51. // platformName: dotnet, framework: google-cloud-functions
  52. if (platformParts.length >= 3) {
  53. return {platformName: platformParts[0], framework: platformParts.slice(1).join('-')};
  54. }
  55. // With some exceptions, all other project platforms have the following two structures:
  56. // 1. "{language}-{framework}", e.g. "javascript-nextjs"
  57. // 2. "{language}", e.g. "python"
  58. const [platformName, framework] = platformParts;
  59. if (platform === 'react-native') {
  60. return {platformName};
  61. }
  62. if (platform.includes('awslambda')) {
  63. return {platformName, framework: 'aws-lambda'};
  64. }
  65. if (platform.includes('gcpfunctions')) {
  66. return {platformName, framework: 'gcp-functions'};
  67. }
  68. return {platformName, framework};
  69. }
  70. export function getCustomInstrumentationLink(project: Project | undefined): string {
  71. // Default to JavaScript guide if project or platform is not available
  72. if (!project || !project.platform) {
  73. return `https://docs.sentry.io/platforms/javascript/tracing/instrumentation/custom-instrumentation/`;
  74. }
  75. const {platformName, framework} = parsePlatform(project.platform);
  76. return platformsWithNestedInstrumentationGuides.includes(project.platform) && framework
  77. ? `https://docs.sentry.io/platforms/${platformName}/guides/${framework}/tracing/instrumentation/custom-instrumentation/`
  78. : `https://docs.sentry.io/platforms/${platformName}/tracing/instrumentation/custom-instrumentation/`;
  79. }
  80. function getDistributedTracingLink(project: Project | undefined): string {
  81. // Default to JavaScript guide if project or platform is not available
  82. if (!project || !project.platform) {
  83. return `https://docs.sentry.io/platforms/javascript/tracing/trace-propagation/`;
  84. }
  85. const {platformName, framework} = parsePlatform(project.platform);
  86. return framework
  87. ? `https://docs.sentry.io/platforms/${platformName}/guides/${framework}/tracing/trace-propagation/`
  88. : `https://docs.sentry.io/platforms/${platformName}/tracing/trace-propagation/`;
  89. }
  90. type ResourceButtonsProps = {
  91. customInstrumentationLink: string;
  92. distributedTracingLink: string;
  93. };
  94. function ResourceButtons({
  95. customInstrumentationLink,
  96. distributedTracingLink,
  97. }: ResourceButtonsProps) {
  98. return (
  99. <ButtonContainer>
  100. <Resource
  101. title={t('Custom Instrumentation')}
  102. subtitle={t('Add Custom Spans or Transactions to your traces')}
  103. link={customInstrumentationLink}
  104. />
  105. <Resource
  106. title={t('Distributed Tracing')}
  107. subtitle={t('See the whole trace across all your services')}
  108. link={distributedTracingLink}
  109. />
  110. </ButtonContainer>
  111. );
  112. }
  113. type TraceConfigurationsProps = {
  114. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  115. };
  116. export default function TraceConfigurations({
  117. rootEventResults,
  118. }: TraceConfigurationsProps) {
  119. const {projects} = useProjects();
  120. const traceProject = useMemo(() => {
  121. return rootEventResults.data
  122. ? projects.find(p => p.id === rootEventResults.data.projectID)
  123. : undefined;
  124. }, [projects, rootEventResults.data]);
  125. const customInstrumentationLink = useMemo(
  126. () => getCustomInstrumentationLink(traceProject),
  127. [traceProject]
  128. );
  129. const distributedTracingLink = useMemo(
  130. () => getDistributedTracingLink(traceProject),
  131. [traceProject]
  132. );
  133. return (
  134. <ClassNames>
  135. {({css}) => (
  136. <Hovercard
  137. body={
  138. <ResourceButtons
  139. customInstrumentationLink={customInstrumentationLink}
  140. distributedTracingLink={distributedTracingLink}
  141. />
  142. }
  143. bodyClassName={css`
  144. padding: ${space(1)};
  145. `}
  146. position="top-end"
  147. >
  148. <Button
  149. size="sm"
  150. icon={<IconQuestion />}
  151. aria-label={t('trace configure resources')}
  152. >
  153. {t('Configure Traces')}
  154. </Button>
  155. </Hovercard>
  156. )}
  157. </ClassNames>
  158. );
  159. }
  160. const ButtonContainer = styled('div')`
  161. display: flex;
  162. flex-direction: column;
  163. gap: ${space(1)};
  164. align-items: flex-start;
  165. `;
  166. const ButtonContent = styled('div')`
  167. display: flex;
  168. flex-direction: column;
  169. text-align: left;
  170. white-space: pre-line;
  171. gap: ${space(0.25)};
  172. `;
  173. const ButtonTitle = styled('div')`
  174. font-weight: ${p => p.theme.fontWeightNormal};
  175. `;
  176. const ButtonSubtitle = styled('div')`
  177. color: ${p => p.theme.gray300};
  178. font-weight: ${p => p.theme.fontWeightNormal};
  179. font-size: ${p => p.theme.fontSizeSmall};
  180. `;
  181. const StyledLinkButton = styled(LinkButton)`
  182. padding: ${space(1)};
  183. height: auto;
  184. `;