loaderSettings.tsx 8.2 KB

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