dynamicSampling.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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. const knowBiases = {
  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.IGNORE_HEALTH_CHECKS]: {
  41. label: t('Ignore health checks'),
  42. help: t('Discards your health checks transactions'),
  43. },
  44. };
  45. export function DynamicSampling({project}: Props) {
  46. const organization = useOrganization();
  47. const api = useApi();
  48. const hasAccess = organization.access.includes('project:write');
  49. const biases = project.dynamicSamplingBiases ?? [];
  50. useEffect(() => {
  51. trackAdvancedAnalyticsEvent('dynamic_sampling_settings.viewed', {
  52. organization,
  53. project_id: project.id,
  54. });
  55. }, [project.id, organization]);
  56. async function handleToggle(bias: DynamicSamplingBias) {
  57. addLoadingMessage();
  58. const newDynamicSamplingBiases = biases.map(b => {
  59. if (b.id === bias.id) {
  60. return {...b, active: !b.active};
  61. }
  62. return b;
  63. });
  64. try {
  65. const result = await api.requestPromise(
  66. `/projects/${organization.slug}/${project.slug}/`,
  67. {
  68. method: 'PUT',
  69. data: {
  70. dynamicSamplingBiases: newDynamicSamplingBiases,
  71. },
  72. }
  73. );
  74. trackAdvancedAnalyticsEvent(
  75. bias.active
  76. ? 'dynamic_sampling_settings.priority_disabled'
  77. : 'dynamic_sampling_settings.priority_enabled',
  78. {
  79. organization,
  80. project_id: project.id,
  81. id: bias.id,
  82. }
  83. );
  84. ProjectsStore.onUpdateSuccess(result);
  85. addSuccessMessage(t('Successfully updated dynamic sampling configuration'));
  86. } catch (error) {
  87. const message = t('Unable to update dynamic sampling configuration');
  88. handleXhrErrorResponse(message)(error);
  89. addErrorMessage(message);
  90. }
  91. }
  92. const handleReadDocs = useCallback(() => {
  93. trackAdvancedAnalyticsEvent('dynamic_sampling_settings.read_docs_clicked', {
  94. organization,
  95. project_id: project.id,
  96. });
  97. }, [organization, project.id]);
  98. return (
  99. <SentryDocumentTitle title={t('Dynamic Sampling')}>
  100. <Fragment>
  101. <SettingsPageHeader
  102. title={
  103. <Fragment>
  104. {t('Dynamic Sampling')} <FeatureBadge type="beta" />
  105. </Fragment>
  106. }
  107. action={<SamplingFeedback />}
  108. />
  109. <TextBlock>
  110. {t(
  111. 'Sentry aims to capture the most valuable transactions in full detail, so you have the necessary data to resolve any performance issues.'
  112. )}
  113. </TextBlock>
  114. <PermissionAlert
  115. organization={organization}
  116. access={['project:write']}
  117. message={t(
  118. 'These settings can only be edited by users with the organization owner, manager, or admin role.'
  119. )}
  120. />
  121. <Panel>
  122. <PanelHeader>{t('Sampling Priorities')}</PanelHeader>
  123. <PanelBody>
  124. {Object.entries(knowBiases).map(([key, value]) => {
  125. const bias = biases.find(b => b.id === key);
  126. if (!bias) {
  127. return null;
  128. }
  129. return (
  130. <BooleanField
  131. {...value}
  132. key={key}
  133. name={key}
  134. value={bias.active}
  135. onChange={() => handleToggle(bias)}
  136. disabled={!hasAccess}
  137. disabledReason={
  138. !hasAccess
  139. ? t('You do not have permission to edit this setting')
  140. : undefined
  141. }
  142. />
  143. );
  144. })}
  145. </PanelBody>
  146. <StyledPanelFooter>
  147. <Button
  148. href={SERVER_SIDE_SAMPLING_DOC_LINK}
  149. onClick={handleReadDocs}
  150. external
  151. >
  152. {t('Read Docs')}
  153. </Button>
  154. </StyledPanelFooter>
  155. </Panel>
  156. </Fragment>
  157. </SentryDocumentTitle>
  158. );
  159. }
  160. const StyledPanelFooter = styled(PanelFooter)`
  161. padding: ${space(1.5)} ${space(2)};
  162. display: flex;
  163. align-items: center;
  164. justify-content: flex-end;
  165. `;