dynamicSampling.tsx 6.0 KB

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