recommendedStepsModal.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import 'prism-sentry/index.css';
  2. import {Fragment, useState} from 'react';
  3. import styled from '@emotion/styled';
  4. import {ModalRenderProps} from 'sentry/actionCreators/modal';
  5. import Button from 'sentry/components/button';
  6. import ButtonBar from 'sentry/components/buttonBar';
  7. import List from 'sentry/components/list';
  8. import ListItem from 'sentry/components/list/listItem';
  9. import {
  10. OutdatedVersion,
  11. SdkOutdatedVersion,
  12. SdkProjectBadge,
  13. UpdatesList,
  14. } from 'sentry/components/sidebar/broadcastSdkUpdates';
  15. import {t, tct} from 'sentry/locale';
  16. import space from 'sentry/styles/space';
  17. import {Organization, Project} from 'sentry/types';
  18. import {
  19. RecommendedSdkUpgrade,
  20. SamplingRule,
  21. UniformModalsSubmit,
  22. } from 'sentry/types/sampling';
  23. import {defined} from 'sentry/utils';
  24. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  25. import {formatPercentage} from 'sentry/utils/formatters';
  26. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  27. import {SamplingProjectIncompatibleAlert} from '../samplingProjectIncompatibleAlert';
  28. import {isValidSampleRate, SERVER_SIDE_SAMPLING_DOC_LINK} from '../utils';
  29. import {projectStatsToSampleRates} from '../utils/projectStatsToSampleRates';
  30. import {useProjectStats} from '../utils/useProjectStats';
  31. import {useRecommendedSdkUpgrades} from '../utils/useRecommendedSdkUpgrades';
  32. import {FooterActions, Stepper} from './uniformRateModal';
  33. export type RecommendedStepsModalProps = ModalRenderProps & {
  34. onReadDocs: () => void;
  35. organization: Organization;
  36. projectId: Project['id'];
  37. recommendedSdkUpgrades: RecommendedSdkUpgrade[];
  38. clientSampleRate?: number;
  39. onGoBack?: () => void;
  40. onSetRules?: (newRules: SamplingRule[]) => void;
  41. onSubmit?: UniformModalsSubmit;
  42. recommendedSampleRate?: boolean;
  43. serverSampleRate?: number;
  44. specifiedClientRate?: number;
  45. uniformRule?: SamplingRule;
  46. };
  47. export function RecommendedStepsModal({
  48. Header,
  49. Body,
  50. Footer,
  51. closeModal,
  52. organization,
  53. recommendedSdkUpgrades,
  54. onGoBack,
  55. onReadDocs,
  56. onSubmit,
  57. clientSampleRate,
  58. serverSampleRate,
  59. uniformRule,
  60. projectId,
  61. specifiedClientRate,
  62. recommendedSampleRate,
  63. onSetRules,
  64. }: RecommendedStepsModalProps) {
  65. const {isProjectIncompatible} = useRecommendedSdkUpgrades({
  66. organization,
  67. projectId,
  68. });
  69. const [saving, setSaving] = useState(false);
  70. const {projectStats48h} = useProjectStats();
  71. const {maxSafeSampleRate} = projectStatsToSampleRates(projectStats48h.data);
  72. const suggestedClientSampleRate = clientSampleRate ?? maxSafeSampleRate;
  73. const isValid =
  74. isValidSampleRate(clientSampleRate) && isValidSampleRate(serverSampleRate);
  75. function handleDone() {
  76. if (!onSubmit) {
  77. closeModal();
  78. }
  79. if (!isValid) {
  80. return;
  81. }
  82. setSaving(true);
  83. onSubmit?.({
  84. recommendedSampleRate: recommendedSampleRate ?? false, // the recommendedSampleRate prop will always be available in the wizard modal
  85. uniformRateModalOrigin: false,
  86. sampleRate: serverSampleRate!,
  87. rule: uniformRule,
  88. onSuccess: newRules => {
  89. setSaving(false);
  90. onSetRules?.(newRules);
  91. closeModal();
  92. },
  93. onError: () => {
  94. setSaving(false);
  95. },
  96. });
  97. }
  98. function handleGoBack() {
  99. if (!onGoBack) {
  100. return;
  101. }
  102. trackAdvancedAnalyticsEvent('sampling.settings.modal.recommended.next.steps_back', {
  103. organization,
  104. project_id: projectId,
  105. });
  106. onGoBack();
  107. }
  108. function handleReadDocs() {
  109. trackAdvancedAnalyticsEvent(
  110. 'sampling.settings.modal.recommended.next.steps_read_docs',
  111. {
  112. organization,
  113. project_id: projectId,
  114. }
  115. );
  116. onReadDocs();
  117. }
  118. return (
  119. <Fragment>
  120. <Header closeButton>
  121. <h4>{t('Important next steps')}</h4>
  122. </Header>
  123. <Body>
  124. <List symbol="colored-numeric">
  125. {!!recommendedSdkUpgrades.length && (
  126. <ListItem>
  127. <h5>{t('Update the following SDK versions')}</h5>
  128. <TextBlock>
  129. {t(
  130. 'To activate Dynamic Sampling rules, it’s a requirement to update the following project SDK(s):'
  131. )}
  132. </TextBlock>
  133. <UpgradeSDKfromProjects>
  134. {recommendedSdkUpgrades.map(
  135. ({project: upgradableProject, latestSDKName, latestSDKVersion}) => {
  136. return (
  137. <div key={upgradableProject.id}>
  138. <SdkProjectBadge
  139. project={upgradableProject}
  140. organization={organization}
  141. />
  142. <SdkOutdatedVersion>
  143. {tct('This project is on [current-version]', {
  144. ['current-version']: (
  145. <OutdatedVersion>{`${latestSDKName}@v${latestSDKVersion}`}</OutdatedVersion>
  146. ),
  147. })}
  148. </SdkOutdatedVersion>
  149. </div>
  150. );
  151. }
  152. )}
  153. </UpgradeSDKfromProjects>
  154. </ListItem>
  155. )}
  156. <ListItem>
  157. <h5>{t('Adjust your Client-Side (SDK) sample rate')}</h5>
  158. <TextBlock>
  159. {t(
  160. 'Here’s the new Client-Side (SDK) sample rate you specified in the previous step. To make this change, find the ‘tracesSampleRate’ option in your SDK Config, modify it’s value to what’s shown below and re-deploy.'
  161. )}
  162. </TextBlock>
  163. <div>
  164. <pre className="language-javascript highlight">
  165. <code className="language-javascript">
  166. Sentry
  167. <span className="token punctuation">.</span>
  168. <span className="token function">init</span>
  169. <span className="token punctuation">(</span>
  170. <span className="token punctuation">{'{'}</span>
  171. <span className="token comment">
  172. {' // '}
  173. {t('JavaScript Example')}
  174. </span>
  175. <br />
  176. <span className="token punctuation">{' ...'}</span>
  177. <br />
  178. <span className="token literal-property property">
  179. {' tracesSampleRate'}
  180. </span>
  181. <span className="token operator">:</span>{' '}
  182. <span className="token string">{suggestedClientSampleRate || ''}</span>
  183. <span className="token punctuation">,</span>{' '}
  184. <span className="token comment">
  185. //{' '}
  186. {suggestedClientSampleRate
  187. ? formatPercentage(suggestedClientSampleRate)
  188. : ''}
  189. </span>
  190. <br />
  191. <span className="token punctuation">{'}'}</span>
  192. <span className="token punctuation">)</span>
  193. <span className="token punctuation">;</span>
  194. </code>
  195. </pre>
  196. </div>
  197. <SamplingProjectIncompatibleAlert
  198. organization={organization}
  199. projectId={projectId}
  200. isProjectIncompatible={isProjectIncompatible}
  201. />
  202. </ListItem>
  203. </List>
  204. </Body>
  205. <Footer>
  206. <FooterActions>
  207. <Button href={SERVER_SIDE_SAMPLING_DOC_LINK} onClick={handleReadDocs} external>
  208. {t('Read Docs')}
  209. </Button>
  210. <ButtonBar gap={1}>
  211. {onGoBack && (
  212. <Fragment>
  213. <Stepper>
  214. {defined(specifiedClientRate) ? t('Step 3 of 3') : t('Step 2 of 2')}
  215. </Stepper>
  216. <Button onClick={handleGoBack}>{t('Back')}</Button>
  217. </Fragment>
  218. )}
  219. {!onGoBack && <Button onClick={closeModal}>{t('Cancel')}</Button>}
  220. <Button
  221. priority="primary"
  222. onClick={handleDone}
  223. disabled={onSubmit ? saving || !isValid || isProjectIncompatible : false} // do not disable the button if there's on onSubmit handler (modal was opened from the sdk alert)
  224. title={
  225. onSubmit
  226. ? !isValid
  227. ? t('Sample rate is not valid')
  228. : undefined
  229. : undefined
  230. }
  231. >
  232. {t('Done')}
  233. </Button>
  234. </ButtonBar>
  235. </FooterActions>
  236. </Footer>
  237. </Fragment>
  238. );
  239. }
  240. const UpgradeSDKfromProjects = styled(UpdatesList)`
  241. margin-top: 0;
  242. margin-bottom: ${space(3)};
  243. `;