loaderSettings.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import {Fragment, useCallback, useEffect, useState} from 'react';
  2. import {
  3. addErrorMessage,
  4. addLoadingMessage,
  5. addSuccessMessage,
  6. } from 'sentry/actionCreators/indicator';
  7. import Access from 'sentry/components/acl/access';
  8. import FieldGroup from 'sentry/components/forms/fieldGroup';
  9. import BooleanField from 'sentry/components/forms/fields/booleanField';
  10. import SelectField from 'sentry/components/forms/fields/selectField';
  11. import ExternalLink from 'sentry/components/links/externalLink';
  12. import TextCopyInput from 'sentry/components/textCopyInput';
  13. import {t, tct} from 'sentry/locale';
  14. import {Project} from 'sentry/types';
  15. import getDynamicText from 'sentry/utils/getDynamicText';
  16. import handleXhrErrorResponse from 'sentry/utils/handleXhrErrorResponse';
  17. import useApi from 'sentry/utils/useApi';
  18. import {ProjectKey} from 'sentry/views/settings/project/projectKeys/types';
  19. type Props = {
  20. keyId: string;
  21. orgSlug: string;
  22. project: Project;
  23. projectKey: ProjectKey;
  24. };
  25. export enum DynamicSDKLoaderOption {
  26. HAS_DEBUG = 'hasDebug',
  27. HAS_PERFORMANCE = 'hasPerformance',
  28. HAS_REPLAY = 'hasReplay',
  29. }
  30. export const sdkLoaderOptions = {
  31. [DynamicSDKLoaderOption.HAS_PERFORMANCE]: {
  32. label: t('Enable Performance Monitoring'),
  33. requiresV7: true,
  34. },
  35. [DynamicSDKLoaderOption.HAS_REPLAY]: {
  36. label: t('Enable Session Replay'),
  37. requiresV7: true,
  38. },
  39. [DynamicSDKLoaderOption.HAS_DEBUG]: {
  40. label: t('Enable Debug Bundles & Logging'),
  41. requiresV7: false,
  42. },
  43. };
  44. export function LoaderSettings({keyId, orgSlug, project, projectKey}: Props) {
  45. const api = useApi();
  46. const [browserSdkVersion, setBrowserSdkVersion] = useState(
  47. projectKey.browserSdkVersion
  48. );
  49. const [dynamicSDKLoaderOptions, setDynamicSDKLoaderOptions] = useState(
  50. projectKey.dynamicSdkLoaderOptions
  51. );
  52. useEffect(() => {
  53. setBrowserSdkVersion(projectKey.browserSdkVersion);
  54. }, [projectKey.browserSdkVersion]);
  55. useEffect(() => {
  56. setDynamicSDKLoaderOptions(projectKey.dynamicSdkLoaderOptions);
  57. }, [projectKey.dynamicSdkLoaderOptions]);
  58. const apiEndpoint = `/projects/${orgSlug}/${project.slug}/keys/${keyId}/`;
  59. const loaderLink = getDynamicText({
  60. value: projectKey.dsn.cdn,
  61. fixed: '__JS_SDK_LOADER_URL__',
  62. });
  63. const handleToggleDynamicSDKLoaderOption = useCallback(
  64. async <T extends typeof dynamicSDKLoaderOptions, K extends keyof T>(
  65. dynamicSdkLoaderOption: K,
  66. value: T[K]
  67. ) => {
  68. const newDynamicSdkLoaderOptions = Object.keys(dynamicSDKLoaderOptions).reduce(
  69. (acc, key) => {
  70. if (key === dynamicSdkLoaderOption) {
  71. return {...acc, [key]: value};
  72. }
  73. return {...acc, [key]: dynamicSDKLoaderOptions[key]};
  74. },
  75. {}
  76. );
  77. addLoadingMessage();
  78. try {
  79. const response = await api.requestPromise(apiEndpoint, {
  80. method: 'PUT',
  81. data: {
  82. dynamicSdkLoaderOptions: newDynamicSdkLoaderOptions,
  83. },
  84. });
  85. setDynamicSDKLoaderOptions(response.dynamicSdkLoaderOptions);
  86. addSuccessMessage(t('Successfully updated dynamic SDK loader configuration'));
  87. } catch (error) {
  88. const message = t('Unable to updated dynamic SDK loader configuration');
  89. handleXhrErrorResponse(message)(error);
  90. addErrorMessage(message);
  91. }
  92. },
  93. [api, apiEndpoint, dynamicSDKLoaderOptions, setDynamicSDKLoaderOptions]
  94. );
  95. const handleUpdateBrowserSDKVersion = useCallback(
  96. async (newBrowserSDKVersion: typeof browserSdkVersion) => {
  97. addLoadingMessage();
  98. const apiData: {
  99. browserSdkVersion: typeof browserSdkVersion;
  100. dynamicSdkLoaderOptions?: Partial<Record<DynamicSDKLoaderOption, boolean>>;
  101. } = {
  102. browserSdkVersion: newBrowserSDKVersion,
  103. };
  104. const shouldRestrictDynamicSdkLoaderOptions =
  105. !sdkVersionSupportsPerformanceAndReplay(newBrowserSDKVersion);
  106. if (shouldRestrictDynamicSdkLoaderOptions) {
  107. // Performance & Replay are not supported before 7.x
  108. const newDynamicSdkLoaderOptions = {
  109. ...dynamicSDKLoaderOptions,
  110. hasPerformance: false,
  111. hasReplay: false,
  112. };
  113. apiData.dynamicSdkLoaderOptions = newDynamicSdkLoaderOptions;
  114. }
  115. try {
  116. const response = await api.requestPromise(apiEndpoint, {
  117. method: 'PUT',
  118. data: apiData,
  119. });
  120. setBrowserSdkVersion(response.browserSdkVersion);
  121. if (shouldRestrictDynamicSdkLoaderOptions) {
  122. setDynamicSDKLoaderOptions(response.dynamicSdkLoaderOptions);
  123. }
  124. addSuccessMessage(t('Successfully updated SDK version'));
  125. } catch (error) {
  126. const message = t('Unable to updated SDK version');
  127. handleXhrErrorResponse(message)(error);
  128. addErrorMessage(message);
  129. }
  130. },
  131. [
  132. api,
  133. apiEndpoint,
  134. setBrowserSdkVersion,
  135. setDynamicSDKLoaderOptions,
  136. dynamicSDKLoaderOptions,
  137. ]
  138. );
  139. return (
  140. <Access access={['project:write']} project={project}>
  141. {({hasAccess}) => (
  142. <Fragment>
  143. <FieldGroup
  144. help={tct(
  145. 'Copy this script into your website to setup your JavaScript SDK without any additional configuration. [link]',
  146. {
  147. link: (
  148. <ExternalLink href="https://docs.sentry.io/platforms/javascript/install/lazy-load-sentry/">
  149. {t(' What does the script provide?')}
  150. </ExternalLink>
  151. ),
  152. }
  153. )}
  154. inline={false}
  155. flexibleControlStateSize
  156. >
  157. <TextCopyInput>
  158. {`<script src='${loaderLink}' crossorigin="anonymous"></script>`}
  159. </TextCopyInput>
  160. </FieldGroup>
  161. <SelectField
  162. name="browserSdkVersion"
  163. label={t('SDK Version')}
  164. options={
  165. projectKey.browserSdk
  166. ? projectKey.browserSdk.choices.map(([value, label]) => ({
  167. value,
  168. label,
  169. }))
  170. : []
  171. }
  172. value={browserSdkVersion}
  173. onChange={handleUpdateBrowserSDKVersion}
  174. placeholder="7.x"
  175. allowClear={false}
  176. disabled={!hasAccess}
  177. />
  178. {Object.entries(sdkLoaderOptions).map(([key, value]) => {
  179. const sdkLoaderOption = Object.keys(dynamicSDKLoaderOptions).find(
  180. dynamicSdkLoaderOption => dynamicSdkLoaderOption === key
  181. );
  182. if (!sdkLoaderOption) {
  183. return null;
  184. }
  185. return (
  186. <BooleanField
  187. label={value.label}
  188. key={key}
  189. name={key}
  190. value={
  191. value.requiresV7 &&
  192. !sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)
  193. ? false
  194. : dynamicSDKLoaderOptions[sdkLoaderOption]
  195. }
  196. onChange={() =>
  197. handleToggleDynamicSDKLoaderOption(
  198. sdkLoaderOption as DynamicSDKLoaderOption,
  199. !dynamicSDKLoaderOptions[sdkLoaderOption]
  200. )
  201. }
  202. disabled={
  203. !hasAccess ||
  204. (value.requiresV7 &&
  205. !sdkVersionSupportsPerformanceAndReplay(browserSdkVersion))
  206. }
  207. help={
  208. value.requiresV7 &&
  209. !sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)
  210. ? t('Only available in SDK version 7.x and above')
  211. : key === DynamicSDKLoaderOption.HAS_REPLAY &&
  212. dynamicSDKLoaderOptions[sdkLoaderOption]
  213. ? t(
  214. 'When using Replay, the loader will load the ES6 bundle instead of the ES5 bundle.'
  215. )
  216. : undefined
  217. }
  218. disabledReason={
  219. !hasAccess
  220. ? t('You do not have permission to edit this setting')
  221. : undefined
  222. }
  223. />
  224. );
  225. })}
  226. </Fragment>
  227. )}
  228. </Access>
  229. );
  230. }
  231. function sdkVersionSupportsPerformanceAndReplay(sdkVersion: string): boolean {
  232. return sdkVersion === 'latest' || sdkVersion === '7.x';
  233. }