import {Fragment, useCallback, useEffect, useState} from 'react'; import { addErrorMessage, addLoadingMessage, addSuccessMessage, } from 'sentry/actionCreators/indicator'; import Access from 'sentry/components/acl/access'; import FieldGroup from 'sentry/components/forms/fieldGroup'; import BooleanField from 'sentry/components/forms/fields/booleanField'; import SelectField from 'sentry/components/forms/fields/selectField'; import ExternalLink from 'sentry/components/links/externalLink'; import TextCopyInput from 'sentry/components/textCopyInput'; import {t, tct} from 'sentry/locale'; import {Project} from 'sentry/types'; import getDynamicText from 'sentry/utils/getDynamicText'; import handleXhrErrorResponse from 'sentry/utils/handleXhrErrorResponse'; import useApi from 'sentry/utils/useApi'; import {ProjectKey} from 'sentry/views/settings/project/projectKeys/types'; type Props = { keyId: string; orgSlug: string; project: Project; projectKey: ProjectKey; }; export enum DynamicSDKLoaderOption { HAS_DEBUG = 'hasDebug', HAS_PERFORMANCE = 'hasPerformance', HAS_REPLAY = 'hasReplay', } export const sdkLoaderOptions = { [DynamicSDKLoaderOption.HAS_PERFORMANCE]: { label: t('Enable Performance Monitoring'), requiresV7: true, }, [DynamicSDKLoaderOption.HAS_REPLAY]: { label: t('Enable Session Replay'), requiresV7: true, }, [DynamicSDKLoaderOption.HAS_DEBUG]: { label: t('Enable Debug Bundles & Logging'), requiresV7: false, }, }; export function LoaderSettings({keyId, orgSlug, project, projectKey}: Props) { const api = useApi(); const [browserSdkVersion, setBrowserSdkVersion] = useState( projectKey.browserSdkVersion ); const [dynamicSDKLoaderOptions, setDynamicSDKLoaderOptions] = useState( projectKey.dynamicSdkLoaderOptions ); useEffect(() => { setBrowserSdkVersion(projectKey.browserSdkVersion); }, [projectKey.browserSdkVersion]); useEffect(() => { setDynamicSDKLoaderOptions(projectKey.dynamicSdkLoaderOptions); }, [projectKey.dynamicSdkLoaderOptions]); const apiEndpoint = `/projects/${orgSlug}/${project.slug}/keys/${keyId}/`; const loaderLink = getDynamicText({ value: projectKey.dsn.cdn, fixed: '__JS_SDK_LOADER_URL__', }); const handleToggleDynamicSDKLoaderOption = useCallback( async ( dynamicSdkLoaderOption: K, value: T[K] ) => { const newDynamicSdkLoaderOptions = Object.keys(dynamicSDKLoaderOptions).reduce( (acc, key) => { if (key === dynamicSdkLoaderOption) { return {...acc, [key]: value}; } return {...acc, [key]: dynamicSDKLoaderOptions[key]}; }, {} ); addLoadingMessage(); try { const response = await api.requestPromise(apiEndpoint, { method: 'PUT', data: { dynamicSdkLoaderOptions: newDynamicSdkLoaderOptions, }, }); setDynamicSDKLoaderOptions(response.dynamicSdkLoaderOptions); addSuccessMessage(t('Successfully updated dynamic SDK loader configuration')); } catch (error) { const message = t('Unable to updated dynamic SDK loader configuration'); handleXhrErrorResponse(message)(error); addErrorMessage(message); } }, [api, apiEndpoint, dynamicSDKLoaderOptions, setDynamicSDKLoaderOptions] ); const handleUpdateBrowserSDKVersion = useCallback( async (newBrowserSDKVersion: typeof browserSdkVersion) => { addLoadingMessage(); const apiData: { browserSdkVersion: typeof browserSdkVersion; dynamicSdkLoaderOptions?: Partial>; } = { browserSdkVersion: newBrowserSDKVersion, }; const shouldRestrictDynamicSdkLoaderOptions = !sdkVersionSupportsPerformanceAndReplay(newBrowserSDKVersion); if (shouldRestrictDynamicSdkLoaderOptions) { // Performance & Replay are not supported before 7.x const newDynamicSdkLoaderOptions = { ...dynamicSDKLoaderOptions, hasPerformance: false, hasReplay: false, }; apiData.dynamicSdkLoaderOptions = newDynamicSdkLoaderOptions; } try { const response = await api.requestPromise(apiEndpoint, { method: 'PUT', data: apiData, }); setBrowserSdkVersion(response.browserSdkVersion); if (shouldRestrictDynamicSdkLoaderOptions) { setDynamicSDKLoaderOptions(response.dynamicSdkLoaderOptions); } addSuccessMessage(t('Successfully updated SDK version')); } catch (error) { const message = t('Unable to updated SDK version'); handleXhrErrorResponse(message)(error); addErrorMessage(message); } }, [ api, apiEndpoint, setBrowserSdkVersion, setDynamicSDKLoaderOptions, dynamicSDKLoaderOptions, ] ); return ( {({hasAccess}) => ( {t(' What does the script provide?')} ), } )} inline={false} flexibleControlStateSize > {``} ({ value, label, })) : [] } value={browserSdkVersion} onChange={handleUpdateBrowserSDKVersion} placeholder="7.x" allowClear={false} disabled={!hasAccess} /> {Object.entries(sdkLoaderOptions).map(([key, value]) => { const sdkLoaderOption = Object.keys(dynamicSDKLoaderOptions).find( dynamicSdkLoaderOption => dynamicSdkLoaderOption === key ); if (!sdkLoaderOption) { return null; } return ( handleToggleDynamicSDKLoaderOption( sdkLoaderOption as DynamicSDKLoaderOption, !dynamicSDKLoaderOptions[sdkLoaderOption] ) } disabled={ !hasAccess || (value.requiresV7 && !sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)) } help={ value.requiresV7 && !sdkVersionSupportsPerformanceAndReplay(browserSdkVersion) ? t('Only available in SDK version 7.x and above') : key === DynamicSDKLoaderOption.HAS_REPLAY && dynamicSDKLoaderOptions[sdkLoaderOption] ? t( 'When using Replay, the loader will load the ES6 bundle instead of the ES5 bundle.' ) : undefined } disabledReason={ !hasAccess ? t('You do not have permission to edit this setting') : undefined } /> ); })} )} ); } function sdkVersionSupportsPerformanceAndReplay(sdkVersion: string): boolean { return sdkVersion === 'latest' || sdkVersion === '7.x'; }