samplingBreakdown.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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?.project_breakdown;
  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. <ProjectBadge 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. 'Sampling rules defined here can also affect other projects. [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. {t(
  106. 'There were no traces initiated from this project in the last 30 days.'
  107. )}
  108. </EmptyMessage>
  109. )}
  110. </Fragment>
  111. )}
  112. </PanelBody>
  113. </Panel>
  114. );
  115. }
  116. const TitleWrapper = styled('div')`
  117. display: flex;
  118. gap: ${space(1)};
  119. align-items: center;
  120. margin-bottom: ${space(1.5)};
  121. `;
  122. const Projects = styled('div')`
  123. display: flex;
  124. flex-wrap: wrap;
  125. gap: ${space(1.5)};
  126. justify-content: flex-start;
  127. align-items: center;
  128. margin-top: ${space(1.5)};
  129. `;
  130. const ProjectWithPercentage = styled('div')`
  131. display: flex;
  132. align-items: center;
  133. gap: ${space(0.5)};
  134. color: ${p => p.theme.subText};
  135. `;
  136. const EmptyMessage = styled('div')`
  137. display: flex;
  138. align-items: center;
  139. min-height: 25px;
  140. color: ${p => p.theme.subText};
  141. `;