projectsPreviewTable.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import {Fragment, useMemo} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import Panel from 'sentry/components/panels/panel';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import {useDebouncedValue} from 'sentry/utils/useDebouncedValue';
  9. import {ProjectsTable} from 'sentry/views/settings/dynamicSampling/projectsTable';
  10. import {SamplingBreakdown} from 'sentry/views/settings/dynamicSampling/samplingBreakdown';
  11. import {formatPercent} from 'sentry/views/settings/dynamicSampling/utils/formatPercent';
  12. import {organizationSamplingForm} from 'sentry/views/settings/dynamicSampling/utils/organizationSamplingForm';
  13. import {parsePercent} from 'sentry/views/settings/dynamicSampling/utils/parsePercent';
  14. import {balanceSampleRate} from 'sentry/views/settings/dynamicSampling/utils/rebalancing';
  15. import type {
  16. ProjectionSamplePeriod,
  17. ProjectSampleCount,
  18. } from 'sentry/views/settings/dynamicSampling/utils/useProjectSampleCounts';
  19. const {useFormField} = organizationSamplingForm;
  20. interface Props {
  21. isLoading: boolean;
  22. period: ProjectionSamplePeriod;
  23. sampleCounts: ProjectSampleCount[];
  24. }
  25. export function ProjectsPreviewTable({isLoading, period, sampleCounts}: Props) {
  26. const {value: targetSampleRate, initialValue: initialTargetSampleRate} =
  27. useFormField('targetSampleRate');
  28. const debouncedTargetSampleRate = useDebouncedValue(
  29. targetSampleRate,
  30. // For longer lists we debounce the input to avoid too many re-renders
  31. sampleCounts.length > 100 ? 200 : 0
  32. );
  33. const balancingItems = useMemo(
  34. () =>
  35. sampleCounts.map(item => ({
  36. ...item,
  37. // Add properties to match the BalancingItem type of the balanceSampleRate function
  38. id: item.project.id,
  39. sampleRate: 1,
  40. })),
  41. [sampleCounts]
  42. );
  43. const {balancedItems} = useMemo(() => {
  44. const targetRate = parsePercent(debouncedTargetSampleRate);
  45. return balanceSampleRate({
  46. targetSampleRate: targetRate,
  47. items: balancingItems,
  48. });
  49. }, [debouncedTargetSampleRate, balancingItems]);
  50. const initialSampleRateById = useMemo(() => {
  51. const targetRate = parsePercent(initialTargetSampleRate);
  52. const {balancedItems: initialBalancedItems} = balanceSampleRate({
  53. targetSampleRate: targetRate,
  54. items: balancingItems,
  55. });
  56. return initialBalancedItems.reduce((acc, item) => {
  57. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  58. acc[item.id] = item.sampleRate;
  59. return acc;
  60. }, {});
  61. }, [initialTargetSampleRate, balancingItems]);
  62. const itemsWithFormattedNumbers = useMemo(() => {
  63. return balancedItems.map(item => ({
  64. ...item,
  65. sampleRate: formatPercent(item.sampleRate),
  66. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  67. initialSampleRate: formatPercent(initialSampleRateById[item.id]),
  68. }));
  69. }, [balancedItems, initialSampleRateById]);
  70. const breakdownSampleRates = balancedItems.reduce((acc, item) => {
  71. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  72. acc[item.id] = item.sampleRate;
  73. return acc;
  74. }, {});
  75. return (
  76. <Fragment>
  77. <BreakdownPanel>
  78. {isLoading ? (
  79. <LoadingIndicator
  80. css={css`
  81. margin: ${space(4)} 0;
  82. `}
  83. />
  84. ) : (
  85. <SamplingBreakdown
  86. sampleCounts={sampleCounts}
  87. sampleRates={breakdownSampleRates}
  88. />
  89. )}
  90. </BreakdownPanel>
  91. <ProjectsTable
  92. stickyHeaders
  93. rateHeader={t('Estimated Rate')}
  94. inputTooltip={t('To edit project sample rates, switch to manual sampling mode.')}
  95. emptyMessage={t('No active projects found in the selected period.')}
  96. period={period}
  97. isEmpty={!sampleCounts.length}
  98. isLoading={isLoading}
  99. items={itemsWithFormattedNumbers}
  100. />
  101. </Fragment>
  102. );
  103. }
  104. const BreakdownPanel = styled(Panel)`
  105. margin-bottom: ${space(3)};
  106. padding: ${space(2)};
  107. `;