organizationSampling.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import {Fragment, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import {Button} from 'sentry/components/button';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {OnRouteLeave} from 'sentry/utils/reactRouter6Compat/onRouteLeave';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import {ProjectionPeriodControl} from 'sentry/views/settings/dynamicSampling/projectionPeriodControl';
  12. import {ProjectsPreviewTable} from 'sentry/views/settings/dynamicSampling/projectsPreviewTable';
  13. import {SamplingModeSwitch} from 'sentry/views/settings/dynamicSampling/samplingModeSwitch';
  14. import {useHasDynamicSamplingWriteAccess} from 'sentry/views/settings/dynamicSampling/utils/access';
  15. import {organizationSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/organizationSamplingForm';
  16. import {parsePercent} from 'sentry/views/settings/dynamicSampling/utils/parsePercent';
  17. import {
  18. type ProjectionSamplePeriod,
  19. useProjectSampleCounts,
  20. } from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
  21. import {useUpdateOrganization} from 'sentry/views/settings/dynamicSampling/utils/useUpdateOrganization';
  22. const {useFormState, FormProvider} = organizationSamplingForm;
  23. const UNSAVED_CHANGES_MESSAGE = t(
  24. 'You have unsaved changes, are you sure you want to leave?'
  25. );
  26. export function OrganizationSampling() {
  27. const organization = useOrganization();
  28. const hasAccess = useHasDynamicSamplingWriteAccess();
  29. const [period, setPeriod] = useState<ProjectionSamplePeriod>('24h');
  30. const formState = useFormState({
  31. initialValues: {
  32. targetSampleRate: ((organization.targetSampleRate ?? 1) * 100)?.toLocaleString(),
  33. },
  34. });
  35. const sampleCountsQuery = useProjectSampleCounts({period});
  36. const {mutate: updateOrganization, isPending} = useUpdateOrganization();
  37. const handleSubmit = () => {
  38. updateOrganization(
  39. {
  40. targetSampleRate: parsePercent(formState.fields.targetSampleRate.value),
  41. },
  42. {
  43. onSuccess: () => {
  44. addSuccessMessage(t('Changes applied.'));
  45. formState.save();
  46. },
  47. onError: () => {
  48. addErrorMessage(t('Unable to save changes. Please try again.'));
  49. },
  50. }
  51. );
  52. };
  53. const handleReset = () => {
  54. formState.reset();
  55. };
  56. return (
  57. <FormProvider formState={formState}>
  58. <OnRouteLeave
  59. message={UNSAVED_CHANGES_MESSAGE}
  60. when={locationChange =>
  61. locationChange.currentLocation.pathname !==
  62. locationChange.nextLocation.pathname && formState.hasChanged
  63. }
  64. />
  65. <HeadingRow>
  66. <ProjectionPeriodControl period={period} onChange={setPeriod} />
  67. <SamplingModeSwitch />
  68. </HeadingRow>
  69. {sampleCountsQuery.isError ? (
  70. <LoadingError onRetry={sampleCountsQuery.refetch} />
  71. ) : (
  72. <ProjectsPreviewTable
  73. sampleCounts={sampleCountsQuery.data}
  74. isLoading={sampleCountsQuery.isPending}
  75. period={period}
  76. actions={
  77. <Fragment>
  78. <Button disabled={!formState.hasChanged || isPending} onClick={handleReset}>
  79. {t('Reset')}
  80. </Button>
  81. <Tooltip
  82. disabled={hasAccess}
  83. title={t('You do not have permission to update these settings.')}
  84. >
  85. <Button
  86. priority="primary"
  87. disabled={
  88. !hasAccess || !formState.isValid || !formState.hasChanged || isPending
  89. }
  90. onClick={handleSubmit}
  91. >
  92. {t('Save changes')}
  93. </Button>
  94. </Tooltip>
  95. </Fragment>
  96. }
  97. />
  98. )}
  99. <SubTextParagraph>
  100. {t('Inactive projects are not listed and will be sampled at 100% initially.')}
  101. </SubTextParagraph>
  102. </FormProvider>
  103. );
  104. }
  105. const HeadingRow = styled('div')`
  106. display: flex;
  107. align-items: center;
  108. justify-content: space-between;
  109. padding-bottom: ${space(1.5)};
  110. & > h4 {
  111. margin: 0;
  112. }
  113. `;
  114. const SubTextParagraph = styled('p')`
  115. color: ${p => p.theme.subText};
  116. font-size: ${p => p.theme.fontSizeSmall};
  117. `;