dynamicSampling.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import {Fragment, useCallback, useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import {
  4. addErrorMessage,
  5. addLoadingMessage,
  6. addSuccessMessage,
  7. } from 'sentry/actionCreators/indicator';
  8. import {Button} from 'sentry/components/button';
  9. import FeatureBadge from 'sentry/components/featureBadge';
  10. import BooleanField from 'sentry/components/forms/fields/booleanField';
  11. import {Panel, PanelBody, PanelFooter, PanelHeader} from 'sentry/components/panels';
  12. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  13. import {t} from 'sentry/locale';
  14. import ProjectsStore from 'sentry/stores/projectsStore';
  15. import space from 'sentry/styles/space';
  16. import {Project} from 'sentry/types';
  17. import {DynamicSamplingBias, DynamicSamplingBiasType} from 'sentry/types/sampling';
  18. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  19. import handleXhrErrorResponse from 'sentry/utils/handleXhrErrorResponse';
  20. import useApi from 'sentry/utils/useApi';
  21. import useOrganization from 'sentry/utils/useOrganization';
  22. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  23. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  24. import PermissionAlert from 'sentry/views/settings/organization/permissionAlert';
  25. import {SamplingFeedback} from './samplingFeedback';
  26. const SERVER_SIDE_SAMPLING_DOC_LINK =
  27. 'https://docs.sentry.io/product/data-management-settings/dynamic-sampling/';
  28. type Props = {
  29. project: Project;
  30. };
  31. export const knowDynamicSamplingBiases = {
  32. [DynamicSamplingBiasType.BOOST_LATEST_RELEASES]: {
  33. label: t('Prioritize new releases'),
  34. help: t('Captures more transactions for your new releases as they are being adopted'),
  35. },
  36. [DynamicSamplingBiasType.BOOST_ENVIRONMENTS]: {
  37. label: t('Prioritize dev environments'),
  38. help: t('Captures more traces from environments that contain “dev” and “test”'),
  39. },
  40. [DynamicSamplingBiasType.BOOST_KEY_TRANSACTIONS]: {
  41. label: t('Prioritize key transactions'),
  42. help: t('Captures more of your most important (starred) transactions'),
  43. },
  44. [DynamicSamplingBiasType.IGNORE_HEALTH_CHECKS]: {
  45. label: t('Ignore health checks'),
  46. help: t('Discards your health checks transactions'),
  47. },
  48. };
  49. export function DynamicSampling({project}: Props) {
  50. const organization = useOrganization();
  51. const api = useApi();
  52. const hasAccess = organization.access.includes('project:write');
  53. const biases = project.dynamicSamplingBiases ?? [];
  54. useEffect(() => {
  55. trackAdvancedAnalyticsEvent('dynamic_sampling_settings.viewed', {
  56. organization,
  57. project_id: project.id,
  58. });
  59. }, [project.id, organization]);
  60. async function handleToggle(bias: DynamicSamplingBias) {
  61. addLoadingMessage();
  62. const newDynamicSamplingBiases = biases.map(b => {
  63. if (b.id === bias.id) {
  64. return {...b, active: !b.active};
  65. }
  66. return b;
  67. });
  68. try {
  69. const result = await api.requestPromise(
  70. `/projects/${organization.slug}/${project.slug}/`,
  71. {
  72. method: 'PUT',
  73. data: {
  74. dynamicSamplingBiases: newDynamicSamplingBiases,
  75. },
  76. }
  77. );
  78. trackAdvancedAnalyticsEvent(
  79. bias.active
  80. ? 'dynamic_sampling_settings.priority_disabled'
  81. : 'dynamic_sampling_settings.priority_enabled',
  82. {
  83. organization,
  84. project_id: project.id,
  85. id: bias.id,
  86. }
  87. );
  88. ProjectsStore.onUpdateSuccess(result);
  89. addSuccessMessage(t('Successfully updated dynamic sampling configuration'));
  90. } catch (error) {
  91. const message = t('Unable to update dynamic sampling configuration');
  92. handleXhrErrorResponse(message)(error);
  93. addErrorMessage(message);
  94. }
  95. }
  96. const handleReadDocs = useCallback(() => {
  97. trackAdvancedAnalyticsEvent('dynamic_sampling_settings.read_docs_clicked', {
  98. organization,
  99. project_id: project.id,
  100. });
  101. }, [organization, project.id]);
  102. return (
  103. <SentryDocumentTitle title={t('Dynamic Sampling')}>
  104. <Fragment>
  105. <SettingsPageHeader
  106. title={
  107. <Fragment>
  108. {t('Dynamic Sampling')} <FeatureBadge type="new" />
  109. </Fragment>
  110. }
  111. action={<SamplingFeedback />}
  112. />
  113. <TextBlock>
  114. {t(
  115. 'Sentry aims to capture the most valuable transactions in full detail, so you have the necessary data to resolve any performance issues.'
  116. )}
  117. </TextBlock>
  118. <PermissionAlert
  119. organization={organization}
  120. access={['project:write']}
  121. message={t(
  122. 'These settings can only be edited by users with the organization owner, manager, or admin role.'
  123. )}
  124. />
  125. <Panel>
  126. <PanelHeader>{t('Sampling Priorities')}</PanelHeader>
  127. <PanelBody>
  128. {Object.entries(knowDynamicSamplingBiases).map(([key, value]) => {
  129. const bias = biases.find(b => b.id === key);
  130. if (!bias) {
  131. return null;
  132. }
  133. return (
  134. <BooleanField
  135. {...value}
  136. key={key}
  137. name={key}
  138. value={bias.active}
  139. onChange={() => handleToggle(bias)}
  140. disabled={!hasAccess}
  141. disabledReason={
  142. !hasAccess
  143. ? t('You do not have permission to edit this setting')
  144. : undefined
  145. }
  146. />
  147. );
  148. })}
  149. </PanelBody>
  150. <StyledPanelFooter>
  151. <Button
  152. href={SERVER_SIDE_SAMPLING_DOC_LINK}
  153. onClick={handleReadDocs}
  154. external
  155. >
  156. {t('Read Docs')}
  157. </Button>
  158. </StyledPanelFooter>
  159. </Panel>
  160. </Fragment>
  161. </SentryDocumentTitle>
  162. );
  163. }
  164. const StyledPanelFooter = styled(PanelFooter)`
  165. padding: ${space(1.5)} ${space(2)};
  166. display: flex;
  167. align-items: center;
  168. justify-content: flex-end;
  169. `;