dynamicSampling.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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 {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('sampling.settings.view', {
  52. organization,
  53. project_id: project.id,
  54. });
  55. }, [project.id, organization]);
  56. async function handleToggle(type: DynamicSamplingBiasType) {
  57. addLoadingMessage();
  58. const newDynamicSamplingBiases = biases.map(bias => {
  59. if (bias.id === type) {
  60. return {...bias, active: !bias.active};
  61. }
  62. return bias;
  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. ProjectsStore.onUpdateSuccess(result);
  75. addSuccessMessage(t('Successfully updated dynamic sampling configuration'));
  76. } catch (error) {
  77. const message = t('Unable to update dynamic sampling configuration');
  78. handleXhrErrorResponse(message)(error);
  79. addErrorMessage(message);
  80. }
  81. }
  82. const handleReadDocs = useCallback(() => {
  83. trackAdvancedAnalyticsEvent('sampling.settings.view_read_docs', {
  84. organization,
  85. project_id: project.id,
  86. });
  87. }, [organization, project.id]);
  88. return (
  89. <SentryDocumentTitle title={t('Dynamic Sampling')}>
  90. <Fragment>
  91. <SettingsPageHeader
  92. title={
  93. <Fragment>
  94. {t('Dynamic Sampling')} <FeatureBadge type="beta" />
  95. </Fragment>
  96. }
  97. action={<SamplingFeedback />}
  98. />
  99. <TextBlock>
  100. {t(
  101. 'Sentry aims to capture the most valuable transactions in full detail, so you have the necessary data to resolve any performance issues.'
  102. )}
  103. </TextBlock>
  104. <PermissionAlert
  105. organization={organization}
  106. access={['project:write']}
  107. message={t(
  108. 'These settings can only be edited by users with the organization owner, manager, or admin role.'
  109. )}
  110. />
  111. <Panel>
  112. <PanelHeader>{t('Sampling Priorities')}</PanelHeader>
  113. <PanelBody>
  114. {Object.entries(knowBiases).map(([key, value]) => {
  115. const bias = biases.find(b => b.id === key);
  116. if (!bias) {
  117. return null;
  118. }
  119. return (
  120. <BooleanField
  121. {...value}
  122. key={key}
  123. name={key}
  124. value={bias.active}
  125. onChange={() => handleToggle(bias.id)}
  126. disabled={!hasAccess}
  127. disabledReason={
  128. !hasAccess
  129. ? t('You do not have permission to edit this setting')
  130. : undefined
  131. }
  132. />
  133. );
  134. })}
  135. </PanelBody>
  136. <StyledPanelFooter>
  137. <Button
  138. href={SERVER_SIDE_SAMPLING_DOC_LINK}
  139. onClick={handleReadDocs}
  140. external
  141. >
  142. {t('Read Docs')}
  143. </Button>
  144. </StyledPanelFooter>
  145. </Panel>
  146. </Fragment>
  147. </SentryDocumentTitle>
  148. );
  149. }
  150. const StyledPanelFooter = styled(PanelFooter)`
  151. padding: ${space(1.5)} ${space(2)};
  152. display: flex;
  153. align-items: center;
  154. justify-content: flex-end;
  155. `;