samplingBreakdown.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {Fragment} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {HeaderTitle} from 'sentry/components/charts/styles';
  5. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  6. import ExternalLink from 'sentry/components/links/externalLink';
  7. import {Panel, PanelBody} from 'sentry/components/panels';
  8. import Placeholder from 'sentry/components/placeholder';
  9. import QuestionTooltip from 'sentry/components/questionTooltip';
  10. import Tooltip from 'sentry/components/tooltip';
  11. import {t, tct} from 'sentry/locale';
  12. import space from 'sentry/styles/space';
  13. import {Organization, Project} from 'sentry/types';
  14. import {percent} from 'sentry/utils';
  15. import {formatPercentage} from 'sentry/utils/formatters';
  16. import useProjects from 'sentry/utils/useProjects';
  17. import ColorBar from 'sentry/views/performance/vitalDetail/colorBar';
  18. import {useDistribution} from './utils/useDistribution';
  19. import {SERVER_SIDE_SAMPLING_DOC_LINK} from './utils';
  20. type Props = {
  21. orgSlug: Organization['slug'];
  22. };
  23. export function SamplingBreakdown({orgSlug}: Props) {
  24. const theme = useTheme();
  25. const {distribution, loading} = useDistribution();
  26. const projectBreakdown = distribution?.projectBreakdown;
  27. const {projects} = useProjects({
  28. slugs: projectBreakdown?.map(project => project.project) ?? [],
  29. orgId: orgSlug,
  30. });
  31. const totalCount = projectBreakdown?.reduce(
  32. (acc, project) => acc + project['count()'],
  33. 0
  34. );
  35. const projectsWithPercentages = projects
  36. .map(project => ({
  37. project,
  38. percentage: percent(
  39. projectBreakdown?.find(pb => pb.project === project.slug)?.['count()'] ?? 0,
  40. totalCount ?? 0
  41. ),
  42. }))
  43. .sort((a, z) => z.percentage - a.percentage);
  44. function projectWithPercentage(project: Project, percentage: number) {
  45. return (
  46. <ProjectWithPercentage key={project.slug}>
  47. <StyledProjectBadge project={project} avatarSize={16} />
  48. {formatPercentage(percentage / 100)}
  49. </ProjectWithPercentage>
  50. );
  51. }
  52. return (
  53. <Panel>
  54. <PanelBody withPadding>
  55. <TitleWrapper>
  56. <HeaderTitle>{t('Transaction Breakdown')}</HeaderTitle>
  57. <QuestionTooltip
  58. title={tct(
  59. 'Shows which projects are affected by the sampling decisions this project makes. [learnMore: Learn more]',
  60. {
  61. learnMore: (
  62. <ExternalLink
  63. href={`${SERVER_SIDE_SAMPLING_DOC_LINK}#traces--propagation-of-sampling-decisions`}
  64. />
  65. ),
  66. }
  67. )}
  68. size="sm"
  69. isHoverable
  70. />
  71. </TitleWrapper>
  72. {loading ? (
  73. <Fragment>
  74. <Placeholder height="16px" bottomGutter={1.5} />
  75. <Placeholder height="21px" width="250px" />
  76. </Fragment>
  77. ) : (
  78. <Fragment>
  79. <ColorBar
  80. colorStops={projectsWithPercentages.map(({project, percentage}, index) => ({
  81. color: theme.charts.getColorPalette(projectsWithPercentages.length)[
  82. index
  83. ],
  84. percent: percentage,
  85. renderBarStatus: (barStatus, key) => (
  86. <Tooltip
  87. title={projectWithPercentage(project, percentage)}
  88. skipWrapper
  89. isHoverable
  90. key={key}
  91. >
  92. {barStatus}
  93. </Tooltip>
  94. ),
  95. }))}
  96. />
  97. {projectsWithPercentages.length ? (
  98. <Projects>
  99. {projectsWithPercentages.map(({project, percentage}) =>
  100. projectWithPercentage(project, percentage)
  101. )}
  102. </Projects>
  103. ) : (
  104. <EmptyMessage>
  105. {tct(
  106. 'This project made no [samplingDecisions] within the last 30 days.',
  107. {
  108. samplingDecisions: (
  109. <Tooltip
  110. title={tct(
  111. 'The first transaction in a trace makes the sampling decision for all following transactions. [learnMore: Learn more]',
  112. {
  113. learnMore: (
  114. <ExternalLink
  115. href={`${SERVER_SIDE_SAMPLING_DOC_LINK}#traces--propagation-of-sampling-decisions`}
  116. />
  117. ),
  118. }
  119. )}
  120. showUnderline
  121. isHoverable
  122. >
  123. {t('sampling decisions')}
  124. </Tooltip>
  125. ),
  126. }
  127. )}
  128. </EmptyMessage>
  129. )}
  130. </Fragment>
  131. )}
  132. </PanelBody>
  133. </Panel>
  134. );
  135. }
  136. const TitleWrapper = styled('div')`
  137. display: flex;
  138. gap: ${space(1)};
  139. align-items: center;
  140. margin-bottom: ${space(1.5)};
  141. `;
  142. const Projects = styled('div')`
  143. display: flex;
  144. flex-wrap: wrap;
  145. gap: ${space(1.5)};
  146. justify-content: flex-start;
  147. align-items: center;
  148. margin-top: ${space(1.5)};
  149. `;
  150. const ProjectWithPercentage = styled('div')`
  151. display: grid;
  152. grid-template-columns: 1fr max-content;
  153. align-items: center;
  154. gap: ${space(0.5)};
  155. color: ${p => p.theme.subText};
  156. `;
  157. const EmptyMessage = styled('div')`
  158. display: flex;
  159. align-items: center;
  160. min-height: 25px;
  161. color: ${p => p.theme.subText};
  162. `;
  163. const StyledProjectBadge = styled(ProjectBadge)`
  164. max-width: 100%;
  165. overflow: hidden;
  166. `;