Browse Source

fix(dynamic-sampling): Use toggle for mode switch (#80359)

Use a toggle button for switching the sampling mode and remove the
"display only" form row.

Part of https://github.com/getsentry/projects/issues/354
ArthurKnaus 4 months ago
parent
commit
39f270ce19

+ 2 - 31
static/app/views/settings/dynamicSampling/organizationSampling.tsx

@@ -1,15 +1,11 @@
 import {useState} from 'react';
-import {css} from '@emotion/react';
 import styled from '@emotion/styled';
 
 import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
 import {Button} from 'sentry/components/button';
-import FieldGroup from 'sentry/components/forms/fieldGroup';
-import ExternalLink from 'sentry/components/links/externalLink';
 import Panel from 'sentry/components/panels/panel';
 import PanelBody from 'sentry/components/panels/panelBody';
 import PanelHeader from 'sentry/components/panels/panelHeader';
-import QuestionTooltip from 'sentry/components/questionTooltip';
 import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {Tooltip} from 'sentry/components/tooltip';
 import {t, tct} from 'sentry/locale';
@@ -20,6 +16,7 @@ import {OrganizationSampleRateField} from 'sentry/views/settings/dynamicSampling
 import {ProjectsPreviewTable} from 'sentry/views/settings/dynamicSampling/projectsPreviewTable';
 import {SamplingModeField} from 'sentry/views/settings/dynamicSampling/samplingModeField';
 import {organizationSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/organizationSamplingForm';
+import type {ProjectionSamplePeriod} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
 import {useUpdateOrganization} from 'sentry/views/settings/dynamicSampling/utils/useUpdateOrganization';
 import {useAccess} from 'sentry/views/settings/projectMetrics/access';
 
@@ -31,7 +28,7 @@ const UNSAVED_CHANGES_MESSAGE = t(
 export function OrganizationSampling() {
   const organization = useOrganization();
   const {hasAccess} = useAccess({access: ['org:write']});
-  const [period, setPeriod] = useState<'24h' | '30d'>('24h');
+  const [period, setPeriod] = useState<ProjectionSamplePeriod>('24h');
 
   const formState = useFormState({
     initialValues: {
@@ -68,32 +65,6 @@ export function OrganizationSampling() {
         <Panel>
           <PanelHeader>{t('Automatic Sampling')}</PanelHeader>
           <PanelBody>
-            <FieldGroup
-              label={t('Sampling Mode')}
-              help={t('The current configuration mode for dynamic sampling.')}
-            >
-              <div
-                css={css`
-                  display: flex;
-                  align-items: center;
-                  gap: ${space(1)};
-                `}
-              >
-                {t('Automatic')}{' '}
-                <QuestionTooltip
-                  size="sm"
-                  isHoverable
-                  title={tct(
-                    '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]',
-                    {
-                      link: (
-                        <ExternalLink href="https://docs.sentry.io/product/performance/retention-priorities/" />
-                      ),
-                    }
-                  )}
-                />
-              </div>
-            </FieldGroup>
             <SamplingModeField />
             <OrganizationSampleRateField />
           </PanelBody>

+ 3 - 32
static/app/views/settings/dynamicSampling/projectSampling.tsx

@@ -1,22 +1,19 @@
 import {useMemo, useState} from 'react';
-import {css} from '@emotion/react';
 import styled from '@emotion/styled';
 
 import {addLoadingMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
 import {Button} from 'sentry/components/button';
-import FieldGroup from 'sentry/components/forms/fieldGroup';
-import ExternalLink from 'sentry/components/links/externalLink';
 import Panel from 'sentry/components/panels/panel';
 import PanelBody from 'sentry/components/panels/panelBody';
 import PanelHeader from 'sentry/components/panels/panelHeader';
-import QuestionTooltip from 'sentry/components/questionTooltip';
 import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {Tooltip} from 'sentry/components/tooltip';
-import {t, tct} from 'sentry/locale';
+import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {ProjectsEditTable} from 'sentry/views/settings/dynamicSampling/projectsEditTable';
 import {SamplingModeField} from 'sentry/views/settings/dynamicSampling/samplingModeField';
 import {projectSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/projectSamplingForm';
+import type {ProjectionSamplePeriod} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
 import {
   useGetSamplingProjectRates,
   useUpdateSamplingProjectRates,
@@ -27,7 +24,7 @@ const {useFormState, FormProvider} = projectSamplingForm;
 
 export function ProjectSampling() {
   const {hasAccess} = useAccess({access: ['org:write']});
-  const [period, setPeriod] = useState<'24h' | '30d'>('24h');
+  const [period, setPeriod] = useState<ProjectionSamplePeriod>('24h');
   const {data, isPending} = useGetSamplingProjectRates();
 
   const updateSamplingProjectRates = useUpdateSamplingProjectRates();
@@ -82,32 +79,6 @@ export function ProjectSampling() {
         <Panel>
           <PanelHeader>{t('Manual Sampling')}</PanelHeader>
           <PanelBody>
-            <FieldGroup
-              label={t('Sampling Mode')}
-              help={t('The current configuration mode for dynamic sampling.')}
-            >
-              <div
-                css={css`
-                  display: flex;
-                  align-items: center;
-                  gap: ${space(1)};
-                `}
-              >
-                {t('Manual')}{' '}
-                <QuestionTooltip
-                  size="sm"
-                  isHoverable
-                  title={tct(
-                    'Manual mode allows you to set fixed sample rates for each project. [link:Learn more]',
-                    {
-                      link: (
-                        <ExternalLink href="https://docs.sentry.io/product/performance/retention-priorities/" />
-                      ),
-                    }
-                  )}
-                />
-              </div>
-            </FieldGroup>
             <SamplingModeField />
           </PanelBody>
         </Panel>

+ 5 - 2
static/app/views/settings/dynamicSampling/projectsEditTable.tsx

@@ -14,11 +14,14 @@ import {PercentInput} from 'sentry/views/settings/dynamicSampling/percentInput';
 import {ProjectsTable} from 'sentry/views/settings/dynamicSampling/projectsTable';
 import {SamplingBreakdown} from 'sentry/views/settings/dynamicSampling/samplingBreakdown';
 import {projectSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/projectSamplingForm';
-import {useProjectSampleCounts} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
+import {
+  type ProjectionSamplePeriod,
+  useProjectSampleCounts,
+} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
 
 interface Props {
   isLoading: boolean;
-  period: '24h' | '30d';
+  period: ProjectionSamplePeriod;
 }
 
 const {useFormField} = projectSamplingForm;

+ 5 - 2
static/app/views/settings/dynamicSampling/projectsPreviewTable.tsx

@@ -7,12 +7,15 @@ import {useDebouncedValue} from 'sentry/utils/useDebouncedValue';
 import {ProjectsTable} from 'sentry/views/settings/dynamicSampling/projectsTable';
 import {organizationSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/organizationSamplingForm';
 import {balanceSampleRate} from 'sentry/views/settings/dynamicSampling/utils/rebalancing';
-import {useProjectSampleCounts} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
+import {
+  type ProjectionSamplePeriod,
+  useProjectSampleCounts,
+} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
 
 const {useFormField} = organizationSamplingForm;
 
 interface Props {
-  period: '24h' | '30d';
+  period: ProjectionSamplePeriod;
 }
 
 export function ProjectsPreviewTable({period}: Props) {

+ 5 - 2
static/app/views/settings/dynamicSampling/samplingBreakdown.tsx

@@ -11,13 +11,16 @@ import {
   formatAbbreviatedNumber,
   formatAbbreviatedNumberWithDynamicPrecision,
 } from 'sentry/utils/formatters';
-import {useProjectSampleCounts} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
+import {
+  type ProjectionSamplePeriod,
+  useProjectSampleCounts,
+} from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
 
 const ITEMS_TO_SHOW = 5;
 const palette = CHART_PALETTE[ITEMS_TO_SHOW - 1];
 
 interface Props extends React.HTMLAttributes<HTMLDivElement> {
-  period: '24h' | '30d';
+  period: ProjectionSamplePeriod;
   sampleRates: Record<string, number>;
 }
 

+ 93 - 52
static/app/views/settings/dynamicSampling/samplingModeField.tsx

@@ -1,16 +1,18 @@
 import {Fragment} from 'react';
-import {css} from '@emotion/react';
+import styled from '@emotion/styled';
 
 import {
   addErrorMessage,
   addLoadingMessage,
   addSuccessMessage,
 } from 'sentry/actionCreators/indicator';
-import {Button} from 'sentry/components/button';
-import Confirm from 'sentry/components/confirm';
+import {openConfirmModal} from 'sentry/components/confirm';
 import FieldGroup from 'sentry/components/forms/fieldGroup';
 import ExternalLink from 'sentry/components/links/externalLink';
+import QuestionTooltip from 'sentry/components/questionTooltip';
+import {SegmentedControl} from 'sentry/components/segmentedControl';
 import {t, tct} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
 import useOrganization from 'sentry/utils/useOrganization';
 import {useUpdateOrganization} from 'sentry/views/settings/dynamicSampling/utils/useUpdateOrganization';
 import {useAccess} from 'sentry/views/settings/projectMetrics/access';
@@ -50,65 +52,104 @@ export function SamplingModeField() {
   });
 
   const handleSwitchMode = () => {
-    updateOrganization({
-      samplingMode: samplingMode === 'organization' ? 'project' : 'organization',
+    openConfirmModal({
+      confirmText: t('Switch Mode'),
+      cancelText: t('Cancel'),
+      header: (
+        <h5>
+          {samplingMode === 'organization'
+            ? t('Switch to Manual Mode')
+            : t('Switch to Automatic Mode')}
+        </h5>
+      ),
+      message: (
+        <Fragment>
+          <p>
+            {samplingMode === 'organization'
+              ? switchToManualMessage
+              : switchToAutoMessage}
+          </p>
+          {samplingMode === 'organization' ? (
+            <p>{t('You can switch back to automatic mode at any time.')}</p>
+          ) : (
+            <p>
+              {tct(
+                'By switching [strong:you will lose your manually defined sample rates].',
+                {
+                  strong: <strong />,
+                }
+              )}
+            </p>
+          )}
+        </Fragment>
+      ),
+      onConfirm: () => {
+        updateOrganization({
+          samplingMode: samplingMode === 'organization' ? 'project' : 'organization',
+        });
+      },
     });
   };
 
   return (
     <FieldGroup
       disabled={!hasAccess}
-      label={t('Switch Mode')}
-      help={
-        samplingMode === 'organization'
-          ? t('Take control over the individual sample rates in your projects.')
-          : t('Let Sentry monitor span volume and adjust sample rates automatically.')
-      }
+      label={t('Sampling Mode')}
+      help={t('The current configuration mode for dynamic sampling.')}
     >
-      <Confirm
-        disabled={!hasAccess || isPending}
-        message={
-          <Fragment>
-            <p>
-              {samplingMode === 'organization'
-                ? switchToManualMessage
-                : switchToAutoMessage}
-            </p>
-            {samplingMode === 'organization' ? (
-              <p>{t('You can switch back to automatic mode at any time.')}</p>
-            ) : (
-              <p>
-                {tct(
-                  'By switching [strong:you will lose your manually defined sample rates].',
+      <ControlWrapper>
+        <SegmentedControl
+          disabled={!hasAccess || isPending}
+          label={t('Sampling mode')}
+          value={samplingMode}
+          onChange={handleSwitchMode}
+        >
+          <SegmentedControl.Item key="organization" textValue={t('Automatic')}>
+            <LabelWrapper>
+              {t('Automatic')}
+              <QuestionTooltip
+                isHoverable
+                size="sm"
+                title={tct(
+                  '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]',
                   {
-                    strong: <strong />,
+                    link: (
+                      <ExternalLink href="https://docs.sentry.io/product/performance/retention-priorities/" />
+                    ),
                   }
                 )}
-              </p>
-            )}
-          </Fragment>
-        }
-        header={
-          <h5>
-            {samplingMode === 'organization'
-              ? t('Switch to Manual Mode')
-              : t('Switch to Automatic Mode')}
-          </h5>
-        }
-        confirmText={t('Switch Mode')}
-        cancelText={t('Cancel')}
-        onConfirm={handleSwitchMode}
-      >
-        <Button
-          css={css`
-            width: max-content;
-          `}
-        >
-          {samplingMode === 'organization'
-            ? t('Switch to Manual')
-            : t('Switch to Automatic')}
-        </Button>
-      </Confirm>
+              />
+            </LabelWrapper>
+          </SegmentedControl.Item>
+          <SegmentedControl.Item key="project" textValue={t('Manual')}>
+            <LabelWrapper>
+              {t('Manual')}
+              <QuestionTooltip
+                isHoverable
+                size="sm"
+                title={tct(
+                  'Manual mode allows you to set fixed sample rates for each project. [link:Learn more]',
+                  {
+                    link: (
+                      <ExternalLink href="https://docs.sentry.io/product/performance/retention-priorities/" />
+                    ),
+                  }
+                )}
+              />
+            </LabelWrapper>
+          </SegmentedControl.Item>
+        </SegmentedControl>
+      </ControlWrapper>
     </FieldGroup>
   );
 }
+
+const ControlWrapper = styled('div')`
+  width: max-content;
+`;
+
+const LabelWrapper = styled('div')`
+  display: flex;
+  align-items: center;
+  gap: ${space(1)};
+`;

+ 3 - 1
static/app/views/settings/dynamicSampling/utils/useProjectSampleCounts.tsx

@@ -19,7 +19,9 @@ const metricsQuery: MetricsQueryApiQueryParams[] = [
   },
 ];
 
-export function useProjectSampleCounts({period}: {period: '24h' | '30d'}) {
+export type ProjectionSamplePeriod = '24h' | '30d';
+
+export function useProjectSampleCounts({period}: {period: ProjectionSamplePeriod}) {
   const {projects, fetching} = useProjects();
 
   const {data, isPending, isError, refetch} = useMetricsQuery(