import {Fragment, useCallback, useEffect} from 'react'; import styled from '@emotion/styled'; import { addErrorMessage, addLoadingMessage, addSuccessMessage, } from 'sentry/actionCreators/indicator'; import {Button} from 'sentry/components/button'; import FeatureBadge from 'sentry/components/featureBadge'; import BooleanField from 'sentry/components/forms/fields/booleanField'; import {Panel, PanelBody, PanelFooter, PanelHeader} from 'sentry/components/panels'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; import ProjectsStore from 'sentry/stores/projectsStore'; import space from 'sentry/styles/space'; import {Project} from 'sentry/types'; import {DynamicSamplingBias, DynamicSamplingBiasType} from 'sentry/types/sampling'; import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent'; import handleXhrErrorResponse from 'sentry/utils/handleXhrErrorResponse'; import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import TextBlock from 'sentry/views/settings/components/text/textBlock'; import PermissionAlert from 'sentry/views/settings/organization/permissionAlert'; import {SamplingFeedback} from './samplingFeedback'; const SERVER_SIDE_SAMPLING_DOC_LINK = 'https://docs.sentry.io/product/data-management-settings/dynamic-sampling/'; type Props = { project: Project; }; export const knowDynamicSamplingBiases = { [DynamicSamplingBiasType.BOOST_LATEST_RELEASES]: { label: t('Prioritize new releases'), help: t('Captures more transactions for your new releases as they are being adopted'), }, [DynamicSamplingBiasType.BOOST_ENVIRONMENTS]: { label: t('Prioritize dev environments'), help: t('Captures more traces from environments that contain “dev” and “test”'), }, [DynamicSamplingBiasType.BOOST_KEY_TRANSACTIONS]: { label: t('Prioritize key transactions'), help: t('Captures more of your most important (starred) transactions'), }, [DynamicSamplingBiasType.IGNORE_HEALTH_CHECKS]: { label: t('Ignore health checks'), help: t('Discards your health checks transactions'), }, }; export function DynamicSampling({project}: Props) { const organization = useOrganization(); const api = useApi(); const hasAccess = organization.access.includes('project:write'); const biases = project.dynamicSamplingBiases ?? []; useEffect(() => { trackAdvancedAnalyticsEvent('dynamic_sampling_settings.viewed', { organization, project_id: project.id, }); }, [project.id, organization]); async function handleToggle(bias: DynamicSamplingBias) { addLoadingMessage(); const newDynamicSamplingBiases = biases.map(b => { if (b.id === bias.id) { return {...b, active: !b.active}; } return b; }); try { const result = await api.requestPromise( `/projects/${organization.slug}/${project.slug}/`, { method: 'PUT', data: { dynamicSamplingBiases: newDynamicSamplingBiases, }, } ); trackAdvancedAnalyticsEvent( bias.active ? 'dynamic_sampling_settings.priority_disabled' : 'dynamic_sampling_settings.priority_enabled', { organization, project_id: project.id, id: bias.id, } ); ProjectsStore.onUpdateSuccess(result); addSuccessMessage(t('Successfully updated dynamic sampling configuration')); } catch (error) { const message = t('Unable to update dynamic sampling configuration'); handleXhrErrorResponse(message)(error); addErrorMessage(message); } } const handleReadDocs = useCallback(() => { trackAdvancedAnalyticsEvent('dynamic_sampling_settings.read_docs_clicked', { organization, project_id: project.id, }); }, [organization, project.id]); return ( {t('Dynamic Sampling')} } action={} /> {t( 'Sentry aims to capture the most valuable transactions in full detail, so you have the necessary data to resolve any performance issues.' )} {t('Sampling Priorities')} {Object.entries(knowDynamicSamplingBiases).map(([key, value]) => { const bias = biases.find(b => b.id === key); if (!bias) { return null; } return ( handleToggle(bias)} disabled={!hasAccess} disabledReason={ !hasAccess ? t('You do not have permission to edit this setting') : undefined } /> ); })} ); } const StyledPanelFooter = styled(PanelFooter)` padding: ${space(1.5)} ${space(2)}; display: flex; align-items: center; justify-content: flex-end; `;