loaderSettings.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import {Fragment, useCallback, 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, ProjectKey} 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. type Props = {
  19. data: ProjectKey;
  20. keyId: string;
  21. orgSlug: string;
  22. project: Project;
  23. updateData: (data: ProjectKey) => void;
  24. };
  25. export function LoaderSettings({keyId, orgSlug, project, data, updateData}: Props) {
  26. const api = useApi();
  27. const [requestPending, setRequestPending] = useState(false);
  28. const [optimisticState, setOptimisticState] = useState({
  29. browserSdkVersion: data.browserSdkVersion,
  30. hasDebug: data.dynamicSdkLoaderOptions.hasDebug,
  31. hasPerformance: data.dynamicSdkLoaderOptions.hasPerformance,
  32. hasReplay: data.dynamicSdkLoaderOptions.hasReplay,
  33. });
  34. const values = requestPending
  35. ? optimisticState
  36. : {
  37. browserSdkVersion: data.browserSdkVersion,
  38. hasDebug: data.dynamicSdkLoaderOptions.hasDebug,
  39. hasPerformance: data.dynamicSdkLoaderOptions.hasPerformance,
  40. hasReplay: data.dynamicSdkLoaderOptions.hasReplay,
  41. };
  42. const apiEndpoint = `/projects/${orgSlug}/${project.slug}/keys/${keyId}/`;
  43. const loaderLink = getDynamicText({
  44. value: data.dsn.cdn,
  45. fixed: '__JS_SDK_LOADER_URL__',
  46. });
  47. const updateLoaderOption = useCallback(
  48. async (changes: {
  49. browserSdkVersion?: string;
  50. hasDebug?: boolean;
  51. hasPerformance?: boolean;
  52. hasReplay?: boolean;
  53. }) => {
  54. setRequestPending(true);
  55. setOptimisticState({
  56. browserSdkVersion: data.browserSdkVersion,
  57. hasDebug: data.dynamicSdkLoaderOptions.hasDebug,
  58. hasPerformance: data.dynamicSdkLoaderOptions.hasPerformance,
  59. hasReplay: data.dynamicSdkLoaderOptions.hasReplay,
  60. ...changes,
  61. });
  62. addLoadingMessage();
  63. const browserSdkVersion = changes.browserSdkVersion ?? data.browserSdkVersion;
  64. let payload: any;
  65. if (sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)) {
  66. payload = {
  67. browserSdkVersion,
  68. dynamicSdkLoaderOptions: {
  69. hasDebug: changes.hasDebug ?? data.dynamicSdkLoaderOptions.hasDebug,
  70. hasPerformance:
  71. changes.hasPerformance ?? data.dynamicSdkLoaderOptions.hasPerformance,
  72. hasReplay: changes.hasReplay ?? data.dynamicSdkLoaderOptions.hasReplay,
  73. },
  74. };
  75. } else {
  76. payload = {
  77. browserSdkVersion,
  78. dynamicSdkLoaderOptions: {
  79. hasDebug: changes.hasDebug ?? data.dynamicSdkLoaderOptions.hasDebug,
  80. hasPerformance: false,
  81. hasReplay: false,
  82. },
  83. };
  84. }
  85. try {
  86. const response = await api.requestPromise(apiEndpoint, {
  87. method: 'PUT',
  88. data: payload,
  89. });
  90. updateData(response);
  91. addSuccessMessage(t('Successfully updated dynamic SDK loader configuration'));
  92. } catch (error) {
  93. const message = t('Unable to updated dynamic SDK loader configuration');
  94. handleXhrErrorResponse(message, error);
  95. addErrorMessage(message);
  96. } finally {
  97. setRequestPending(false);
  98. }
  99. },
  100. [
  101. api,
  102. apiEndpoint,
  103. data.browserSdkVersion,
  104. data.dynamicSdkLoaderOptions.hasDebug,
  105. data.dynamicSdkLoaderOptions.hasPerformance,
  106. data.dynamicSdkLoaderOptions.hasReplay,
  107. setRequestPending,
  108. updateData,
  109. ]
  110. );
  111. return (
  112. <Access access={['project:write']} project={project}>
  113. {({hasAccess}) => (
  114. <Fragment>
  115. <FieldGroup
  116. help={tct(
  117. 'Copy this script into your website to setup your JavaScript SDK without any additional configuration. [link]',
  118. {
  119. link: (
  120. <ExternalLink href="https://docs.sentry.io/platforms/javascript/install/lazy-load-sentry/">
  121. {t(' What does the script provide?')}
  122. </ExternalLink>
  123. ),
  124. }
  125. )}
  126. inline={false}
  127. flexibleControlStateSize
  128. >
  129. <TextCopyInput aria-label={t('Loader Script')}>
  130. {`<script src="${loaderLink}" crossorigin="anonymous"></script>`}
  131. </TextCopyInput>
  132. </FieldGroup>
  133. <SelectField
  134. name={`${keyId}-browserSdkVersion`}
  135. label={t('SDK Version')}
  136. options={
  137. data.browserSdk
  138. ? data.browserSdk.choices.map(([value, label]) => ({
  139. value,
  140. label,
  141. }))
  142. : []
  143. }
  144. value={values.browserSdkVersion}
  145. onChange={value => {
  146. updateLoaderOption({browserSdkVersion: value});
  147. }}
  148. placeholder="7.x"
  149. allowClear={false}
  150. disabled={!hasAccess || requestPending}
  151. />
  152. <BooleanField
  153. label={t('Enable Performance Monitoring')}
  154. name={`${keyId}-has-performance`}
  155. value={
  156. sdkVersionSupportsPerformanceAndReplay(data.browserSdkVersion)
  157. ? values.hasPerformance
  158. : false
  159. }
  160. onChange={value => {
  161. updateLoaderOption({hasPerformance: value});
  162. }}
  163. disabled={
  164. !hasAccess ||
  165. requestPending ||
  166. !sdkVersionSupportsPerformanceAndReplay(data.browserSdkVersion)
  167. }
  168. help={
  169. !sdkVersionSupportsPerformanceAndReplay(data.browserSdkVersion)
  170. ? t('Only available in SDK version 7.x and above')
  171. : undefined
  172. }
  173. disabledReason={
  174. !hasAccess
  175. ? t('You do not have permission to edit this setting')
  176. : undefined
  177. }
  178. />
  179. <BooleanField
  180. label={t('Enable Session Replay')}
  181. name={`${keyId}-has-replay`}
  182. value={
  183. sdkVersionSupportsPerformanceAndReplay(data.browserSdkVersion)
  184. ? values.hasReplay
  185. : false
  186. }
  187. onChange={value => {
  188. updateLoaderOption({hasReplay: value});
  189. }}
  190. disabled={
  191. !hasAccess ||
  192. requestPending ||
  193. !sdkVersionSupportsPerformanceAndReplay(data.browserSdkVersion)
  194. }
  195. help={
  196. !sdkVersionSupportsPerformanceAndReplay(data.browserSdkVersion)
  197. ? t('Only available in SDK version 7.x and above')
  198. : data.dynamicSdkLoaderOptions.hasReplay
  199. ? t(
  200. 'When using Replay, the loader will load the ES6 bundle instead of the ES5 bundle.'
  201. )
  202. : undefined
  203. }
  204. disabledReason={
  205. !hasAccess
  206. ? t('You do not have permission to edit this setting')
  207. : undefined
  208. }
  209. />
  210. <BooleanField
  211. label={t('Enable Debug Bundles & Logging')}
  212. name={`${keyId}-has-logging`}
  213. value={values.hasDebug}
  214. onChange={value => {
  215. updateLoaderOption({hasDebug: value});
  216. }}
  217. disabled={!hasAccess || requestPending}
  218. disabledReason={
  219. !hasAccess
  220. ? t('You do not have permission to edit this setting')
  221. : undefined
  222. }
  223. />
  224. </Fragment>
  225. )}
  226. </Access>
  227. );
  228. }
  229. function sdkVersionSupportsPerformanceAndReplay(sdkVersion: string): boolean {
  230. return sdkVersion === 'latest' || sdkVersion === '7.x';
  231. }