projectSampling.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import {useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {addLoadingMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import {Button} from 'sentry/components/button';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import Panel from 'sentry/components/panels/panel';
  7. import PanelBody from 'sentry/components/panels/panelBody';
  8. import PanelHeader from 'sentry/components/panels/panelHeader';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {ProjectionPeriodControl} from 'sentry/views/settings/dynamicSampling/projectionPeriodControl';
  12. import {ProjectsEditTable} from 'sentry/views/settings/dynamicSampling/projectsEditTable';
  13. import {SamplingModeField} from 'sentry/views/settings/dynamicSampling/samplingModeField';
  14. import {useHasDynamicSamplingWriteAccess} from 'sentry/views/settings/dynamicSampling/utils/access';
  15. import {projectSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/projectSamplingForm';
  16. import {
  17. type ProjectionSamplePeriod,
  18. useProjectSampleCounts,
  19. } from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
  20. import {
  21. useGetSamplingProjectRates,
  22. useUpdateSamplingProjectRates,
  23. } from 'sentry/views/settings/dynamicSampling/utils/useSamplingProjectRates';
  24. const {useFormState, FormProvider} = projectSamplingForm;
  25. export function ProjectSampling() {
  26. const hasAccess = useHasDynamicSamplingWriteAccess();
  27. const [period, setPeriod] = useState<ProjectionSamplePeriod>('24h');
  28. const sampleRatesQuery = useGetSamplingProjectRates();
  29. const sampleCountsQuery = useProjectSampleCounts({period});
  30. const updateSamplingProjectRates = useUpdateSamplingProjectRates();
  31. const projectRates = useMemo(
  32. () =>
  33. (sampleRatesQuery.data || []).reduce(
  34. (acc, item) => {
  35. acc[item.id.toString()] = (item.sampleRate * 100).toString();
  36. return acc;
  37. },
  38. {} as Record<string, string>
  39. ),
  40. [sampleRatesQuery.data]
  41. );
  42. const initialValues = useMemo(() => ({projectRates}), [projectRates]);
  43. const formState = useFormState({
  44. initialValues: initialValues,
  45. enableReInitialize: true,
  46. });
  47. const handleSubmit = () => {
  48. const ratesArray = Object.entries(formState.fields.projectRates.value).map(
  49. ([id, rate]) => ({
  50. id: Number(id),
  51. sampleRate: Number(rate) / 100,
  52. })
  53. );
  54. addLoadingMessage(t('Saving changes...'));
  55. updateSamplingProjectRates.mutate(ratesArray, {
  56. onSuccess: () => {
  57. formState.save();
  58. addSuccessMessage(t('Changes applied'));
  59. },
  60. onError: () => {
  61. addLoadingMessage(t('Unable to save changes. Please try again.'));
  62. },
  63. });
  64. };
  65. const isFormActionDisabled =
  66. !hasAccess ||
  67. sampleRatesQuery.isPending ||
  68. updateSamplingProjectRates.isPending ||
  69. !formState.hasChanged;
  70. return (
  71. <FormProvider formState={formState}>
  72. <form onSubmit={event => event.preventDefault()}>
  73. <Panel>
  74. <PanelHeader>{t('Manual Sampling')}</PanelHeader>
  75. <PanelBody>
  76. <SamplingModeField />
  77. </PanelBody>
  78. </Panel>
  79. <HeadingRow>
  80. <h4>{t('Customize Projects')}</h4>
  81. <ProjectionPeriodControl period={period} onChange={setPeriod} />
  82. </HeadingRow>
  83. <p>{t('Set custom rates for traces starting at each of your projects.')}</p>
  84. {sampleCountsQuery.isError ? (
  85. <LoadingError onRetry={sampleCountsQuery.refetch} />
  86. ) : (
  87. <ProjectsEditTable
  88. isLoading={sampleRatesQuery.isPending || sampleCountsQuery.isPending}
  89. sampleCounts={sampleCountsQuery.data}
  90. />
  91. )}
  92. <FormActions>
  93. <Button disabled={isFormActionDisabled} onClick={formState.reset}>
  94. {t('Reset')}
  95. </Button>
  96. <Button
  97. priority="primary"
  98. disabled={isFormActionDisabled}
  99. onClick={handleSubmit}
  100. >
  101. {t('Apply Changes')}
  102. </Button>
  103. </FormActions>
  104. </form>
  105. </FormProvider>
  106. );
  107. }
  108. const FormActions = styled('div')`
  109. display: grid;
  110. grid-template-columns: repeat(2, max-content);
  111. gap: ${space(1)};
  112. justify-content: flex-end;
  113. padding-bottom: ${space(4)};
  114. `;
  115. const HeadingRow = styled('div')`
  116. display: flex;
  117. align-items: center;
  118. justify-content: space-between;
  119. padding-top: ${space(3)};
  120. padding-bottom: ${space(1.5)};
  121. & > * {
  122. margin: 0;
  123. }
  124. `;