organizationSampling.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import {useState} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  5. import {Button} from 'sentry/components/button';
  6. import FieldGroup from 'sentry/components/forms/fieldGroup';
  7. import ExternalLink from 'sentry/components/links/externalLink';
  8. import Panel from 'sentry/components/panels/panel';
  9. import PanelBody from 'sentry/components/panels/panelBody';
  10. import PanelHeader from 'sentry/components/panels/panelHeader';
  11. import QuestionTooltip from 'sentry/components/questionTooltip';
  12. import {SegmentedControl} from 'sentry/components/segmentedControl';
  13. import {Tooltip} from 'sentry/components/tooltip';
  14. import {t, tct} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import {OnRouteLeave} from 'sentry/utils/reactRouter6Compat/onRouteLeave';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {OrganizationSampleRateField} from 'sentry/views/settings/dynamicSampling/organizationSampleRateField';
  19. import {ProjectsPreviewTable} from 'sentry/views/settings/dynamicSampling/projectsPreviewTable';
  20. import {SamplingModeField} from 'sentry/views/settings/dynamicSampling/samplingModeField';
  21. import {organizationSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/organizationSamplingForm';
  22. import {useUpdateOrganization} from 'sentry/views/settings/dynamicSampling/utils/useUpdateOrganization';
  23. import {useAccess} from 'sentry/views/settings/projectMetrics/access';
  24. const {useFormState, FormProvider} = organizationSamplingForm;
  25. const UNSAVED_CHANGES_MESSAGE = t(
  26. 'You have unsaved changes, are you sure you want to leave?'
  27. );
  28. export function OrganizationSampling() {
  29. const organization = useOrganization();
  30. const {hasAccess} = useAccess({access: ['org:write']});
  31. const [period, setPeriod] = useState<'24h' | '30d'>('24h');
  32. const formState = useFormState({
  33. targetSampleRate: ((organization.targetSampleRate ?? 1) * 100)?.toLocaleString(),
  34. });
  35. const {mutate: updateOrganization, isPending} = useUpdateOrganization();
  36. const handleSubmit = () => {
  37. updateOrganization(
  38. {
  39. targetSampleRate: Number(formState.fields.targetSampleRate.value) / 100,
  40. },
  41. {
  42. onSuccess: () => {
  43. addSuccessMessage(t('Changes applied.'));
  44. formState.save();
  45. },
  46. onError: () => {
  47. addErrorMessage(t('Unable to save changes. Please try again.'));
  48. },
  49. }
  50. );
  51. };
  52. const handleReset = () => {
  53. formState.reset();
  54. };
  55. return (
  56. <FormProvider formState={formState}>
  57. <form onSubmit={event => event.preventDefault()}>
  58. <Panel>
  59. <PanelHeader>{t('Automatic Sampling')}</PanelHeader>
  60. <PanelBody>
  61. <FieldGroup
  62. label={t('Sampling Mode')}
  63. help={t('The current configuration mode for dynamic sampling.')}
  64. >
  65. <div
  66. css={css`
  67. display: flex;
  68. align-items: center;
  69. gap: ${space(1)};
  70. `}
  71. >
  72. {t('Automatic')}{' '}
  73. <QuestionTooltip
  74. size="sm"
  75. isHoverable
  76. title={tct(
  77. 'Automatic mode allows you to set a target sample rate for your organization. Sentry automatically adjusts individual project rates to boost small projects and ensure equal visibility. [link:Learn more]',
  78. {
  79. link: (
  80. <ExternalLink href="https://docs.sentry.io/product/performance/retention-priorities/" />
  81. ),
  82. }
  83. )}
  84. />
  85. </div>
  86. </FieldGroup>
  87. <SamplingModeField />
  88. <OrganizationSampleRateField />
  89. </PanelBody>
  90. </Panel>
  91. <OnRouteLeave
  92. message={UNSAVED_CHANGES_MESSAGE}
  93. when={locationChange =>
  94. locationChange.currentLocation.pathname !==
  95. locationChange.nextLocation.pathname && formState.hasChanged
  96. }
  97. />
  98. <FormActions>
  99. <Button disabled={!formState.hasChanged || isPending} onClick={handleReset}>
  100. {t('Reset')}
  101. </Button>
  102. <Tooltip
  103. disabled={hasAccess}
  104. title={t('You do not have permission to update these settings.')}
  105. >
  106. <Button
  107. priority="primary"
  108. disabled={
  109. !hasAccess || !formState.isValid || !formState.hasChanged || isPending
  110. }
  111. onClick={handleSubmit}
  112. >
  113. {t('Save changes')}
  114. </Button>
  115. </Tooltip>
  116. </FormActions>
  117. <HeadingRow>
  118. <h4>{t('Project Preview')}</h4>
  119. <Tooltip
  120. title={t(
  121. 'The time period for which the projected sample rates are calculated.'
  122. )}
  123. >
  124. <SegmentedControl value={period} onChange={setPeriod} size="xs">
  125. <SegmentedControl.Item key="24h">{t('24h')}</SegmentedControl.Item>
  126. <SegmentedControl.Item key="30d">{t('30d')}</SegmentedControl.Item>
  127. </SegmentedControl>
  128. </Tooltip>
  129. </HeadingRow>
  130. <p>
  131. {tct(
  132. 'This table gives you a preview of how your projects will be affected by the global sample rate. The [strong:projected rates are estimates] based on recent span volume.',
  133. {
  134. strong: <strong />,
  135. }
  136. )}
  137. </p>
  138. <ProjectsPreviewTable period={period} />
  139. <SubTextParagraph>
  140. {t('Inactive projects are not listed and will be sampled at 100% initially.')}
  141. </SubTextParagraph>
  142. </form>
  143. </FormProvider>
  144. );
  145. }
  146. const FormActions = styled('div')`
  147. display: grid;
  148. grid-template-columns: repeat(2, max-content);
  149. gap: ${space(1)};
  150. justify-content: flex-end;
  151. padding-bottom: ${space(4)};
  152. `;
  153. const HeadingRow = styled('div')`
  154. display: flex;
  155. align-items: center;
  156. justify-content: space-between;
  157. padding-bottom: ${space(1.5)};
  158. & > h4 {
  159. margin: 0;
  160. }
  161. `;
  162. const SubTextParagraph = styled('p')`
  163. color: ${p => p.theme.subText};
  164. font-size: ${p => p.theme.fontSizeSmall};
  165. `;