From 979cf3fc2a8597b04ae9db79af0840b5b3370fce Mon Sep 17 00:00:00 2001 From: TkDodo Date: Fri, 10 Apr 2026 17:13:31 +0200 Subject: [PATCH 01/14] feat(eslint): no-unnecessary-use-callback --- eslint.config.ts | 1 + static/eslint/eslintPluginSentry/index.ts | 2 + .../no-unnecessary-use-callback.spec.ts | 186 ++++++++++++++++++ .../no-unnecessary-use-callback.ts | 162 +++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 static/eslint/eslintPluginSentry/no-unnecessary-use-callback.spec.ts create mode 100644 static/eslint/eslintPluginSentry/no-unnecessary-use-callback.ts diff --git a/eslint.config.ts b/eslint.config.ts index 3fb04f947cf187..c7558fa5d6ede6 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -461,6 +461,7 @@ export default typescript.config([ '@sentry/no-dynamic-translations': 'error', '@sentry/no-static-translations': 'error', '@sentry/no-styled-shortcut': 'error', + '@sentry/no-unnecessary-use-callback': 'error', }, }, { diff --git a/static/eslint/eslintPluginSentry/index.ts b/static/eslint/eslintPluginSentry/index.ts index 5cb811d210125d..2aad65f698f4f6 100644 --- a/static/eslint/eslintPluginSentry/index.ts +++ b/static/eslint/eslintPluginSentry/index.ts @@ -4,6 +4,7 @@ import {noDynamicTranslations} from './no-dynamic-translations'; import {noStaticTranslations} from './no-static-translations'; import {noStyledShortcut} from './no-styled-shortcut'; import {noUnnecessaryTypeAnnotation} from './no-unnecessary-type-annotation'; +import {noUnnecessaryUseCallback} from './no-unnecessary-use-callback'; export const rules = { 'no-default-exports': noDefaultExports, @@ -12,4 +13,5 @@ export const rules = { 'no-static-translations': noStaticTranslations, 'no-styled-shortcut': noStyledShortcut, 'no-unnecessary-type-annotation': noUnnecessaryTypeAnnotation, + 'no-unnecessary-use-callback': noUnnecessaryUseCallback, }; diff --git a/static/eslint/eslintPluginSentry/no-unnecessary-use-callback.spec.ts b/static/eslint/eslintPluginSentry/no-unnecessary-use-callback.spec.ts new file mode 100644 index 00000000000000..5a854fd781b167 --- /dev/null +++ b/static/eslint/eslintPluginSentry/no-unnecessary-use-callback.spec.ts @@ -0,0 +1,186 @@ +import {RuleTester} from '@typescript-eslint/rule-tester'; + +import {noUnnecessaryUseCallback} from './no-unnecessary-use-callback'; + +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + ecmaFeatures: {jsx: true}, + }, + }, +}); + +ruleTester.run('no-unnecessary-use-callback', noUnnecessaryUseCallback, { + valid: [ + { + name: 'useCallback passed directly to custom component', + code: ` + const fn = useCallback(() => { + console.log('click'); + }, []); + + `, + }, + { + name: 'regular function passed to intrinsic element', + code: ` + const fn = () => console.log('click'); + ; } diff --git a/static/app/views/seerExplorer/blockComponents.tsx b/static/app/views/seerExplorer/blockComponents.tsx index 2bb1f6db1493f6..caff48a5cc3318 100644 --- a/static/app/views/seerExplorer/blockComponents.tsx +++ b/static/app/views/seerExplorer/blockComponents.tsx @@ -287,33 +287,23 @@ export function BlockComponent({ false ); - const trackThumbsFeedback = useCallback( - (type: 'positive' | 'negative') => { - // Guard against missing runId (shouldn't happen with showActions check, but be defensive) - // Do this instead of hiding buttons to prevent flickering while data's loading for this edge case. - if (!feedbackSubmitted && runId !== undefined) { - trackAnalytics('seer.explorer.feedback_submitted', { - organization, - type, - run_id: runId, - block_index: blockIndex, - block_message: block.message.content.slice(0, 100), - langfuse_url: getLangfuseUrl(runId), - explorer_url: getExplorerUrl(runId), - conversations_url: getConversationsUrl('sentry', runId), - }); - setFeedbackSubmitted(true); // disable button for rest of the session - } - }, - [ - organization, - blockIndex, - runId, - block.message.content, - feedbackSubmitted, - setFeedbackSubmitted, - ] - ); + const trackThumbsFeedback = (type: 'positive' | 'negative') => { + // Guard against missing runId (shouldn't happen with showActions check, but be defensive) + // Do this instead of hiding buttons to prevent flickering while data's loading for this edge case. + if (!feedbackSubmitted && runId !== undefined) { + trackAnalytics('seer.explorer.feedback_submitted', { + organization, + type, + run_id: runId, + block_index: blockIndex, + block_message: block.message.content.slice(0, 100), + langfuse_url: getLangfuseUrl(runId), + explorer_url: getExplorerUrl(runId), + conversations_url: getConversationsUrl('sentry', runId), + }); + setFeedbackSubmitted(true); // disable button for rest of the session + } + }; const thumbsFeedbackButton = (type: 'positive' | 'negative') => { const ariaLabel = diff --git a/static/app/views/settings/components/dataScrubbing/modals/dataScrubFormModal.tsx b/static/app/views/settings/components/dataScrubbing/modals/dataScrubFormModal.tsx index 25d9ee24af70e4..fdf7f8259a08b3 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/dataScrubFormModal.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/dataScrubFormModal.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useState} from 'react'; +import {Fragment, useState} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import sortBy from 'lodash/sortBy'; @@ -163,23 +163,20 @@ export function DataScrubFormModal({ const type = useStore(form.store, state => state.values.type); - const handleValidateAttributeField = useCallback( - (value: string) => { - const traceItemField = TraceItemFieldSelector.fromField(dataset, value); + const handleValidateAttributeField = (value: string) => { + const traceItemField = TraceItemFieldSelector.fromField(dataset, value); - if (!traceItemField) { - return; - } + if (!traceItemField) { + return; + } - const validation = validateTraceItemFieldSelector(traceItemField); - if (!validation.isValid && validation.error) { - setFieldErrors(form, { - source: {message: validation.error}, - }); - } - }, - [dataset, form] - ); + const validation = validateTraceItemFieldSelector(traceItemField); + if (!validation.isValid && validation.error) { + setFieldErrors(form, { + source: {message: validation.error}, + }); + } + }; const containsRootDeepWildcard = (source: string) => { return /(^|[^.])\*\*$/.test(source); diff --git a/static/app/views/settings/components/settingsBreadcrumb/breadcrumbDropdown.tsx b/static/app/views/settings/components/settingsBreadcrumb/breadcrumbDropdown.tsx index 24ac941feb4277..f02ea1b382379b 100644 --- a/static/app/views/settings/components/settingsBreadcrumb/breadcrumbDropdown.tsx +++ b/static/app/views/settings/components/settingsBreadcrumb/breadcrumbDropdown.tsx @@ -112,11 +112,11 @@ function MenuCrumb({crumbLabel, menuHasHover, isLast, ...props}: MenuCrumbProps) closeTimeoutRef.current = window.setTimeout(() => close?.(), CLOSE_MENU_TIMEOUT); }, [close]); - const handleOpen = useCallback(() => { + const handleOpen = () => { activeCrumbStates.forEach(state => state?.close()); window.clearTimeout(closeTimeoutRef.current); open?.(); - }, [open]); + }; useEffect(() => { if (menuHasHover) { diff --git a/static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx b/static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx index 429af805dbe652..93883d2592f99a 100644 --- a/static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx +++ b/static/app/views/settings/featureFlags/changeTracking/newProviderForm.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import {z} from 'zod'; import {Button} from '@sentry/scraps/button'; @@ -68,11 +67,11 @@ export function NewProviderForm({ const queryClient = useQueryClient(); const navigate = useNavigate(); - const handleGoBack = useCallback(() => { + const handleGoBack = () => { navigate( normalizeUrl(`/settings/${organization.slug}/feature-flags/change-tracking/`) ); - }, [organization.slug, navigate]); + }; const mutation = useMutation({ mutationFn: ({provider, secret}: CreateSecretData) => { diff --git a/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx b/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx index 64af028cdc9004..31bc016641e4eb 100644 --- a/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx +++ b/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import styled from '@emotion/styled'; import {z} from 'zod'; @@ -43,10 +42,7 @@ function AuthTokenCreateForm({ const navigate = useNavigate(); const queryClient = useQueryClient(); - const handleGoBack = useCallback( - () => navigate(`/settings/${organization.slug}/auth-tokens/`), - [navigate, organization.slug] - ); + const handleGoBack = () => navigate(`/settings/${organization.slug}/auth-tokens/`); const mutation = useMutation< OrgAuthTokenWithToken, @@ -132,10 +128,7 @@ export default function OrganizationAuthTokensNewAuthToken() { const organization = useOrganization(); const navigate = useNavigate(); - const handleGoBack = useCallback( - () => navigate(`/settings/${organization.slug}/auth-tokens/`), - [navigate, organization.slug] - ); + const handleGoBack = () => navigate(`/settings/${organization.slug}/auth-tokens/`); return (
diff --git a/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDashboard/requestLog.tsx b/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDashboard/requestLog.tsx index 4af52bd6160e7a..23f1f4b59a4356 100644 --- a/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDashboard/requestLog.tsx +++ b/static/app/views/settings/organizationDeveloperSettings/sentryApplicationDashboard/requestLog.tsx @@ -170,14 +170,11 @@ export function RequestLog({app}: RequestLogProps) { const hasPrevPage = useMemo(() => currentPage > 0, [currentPage]); - const handleChangeEventType = useCallback( - (newEventType: string) => { - setEventType(newEventType); - setCurrentPage(0); - refetch(); - }, - [refetch] - ); + const handleChangeEventType = (newEventType: string) => { + setEventType(newEventType); + setCurrentPage(0); + refetch(); + }; const handleChangeErrorsOnly = useCallback(() => { setErrorsOnly(!errorsOnly); @@ -185,13 +182,13 @@ export function RequestLog({app}: RequestLogProps) { refetch(); }, [errorsOnly, refetch]); - const handleNextPage = useCallback(() => { + const handleNextPage = () => { setCurrentPage(currentPage + 1); - }, [currentPage]); + }; - const handlePrevPage = useCallback(() => { + const handlePrevPage = () => { setCurrentPage(currentPage - 1); - }, [currentPage]); + }; return ( diff --git a/static/app/views/settings/organizationIntegrations/SplitInstallationIdModal.tsx b/static/app/views/settings/organizationIntegrations/SplitInstallationIdModal.tsx index 4c33c1f700ec29..6f9d1ffe39707c 100644 --- a/static/app/views/settings/organizationIntegrations/SplitInstallationIdModal.tsx +++ b/static/app/views/settings/organizationIntegrations/SplitInstallationIdModal.tsx @@ -30,7 +30,7 @@ export function SplitInstallationIdModal(props: Props) { await navigator.clipboard.writeText(props.installationId); }, [props.installationId]); - const handleContinue = useCallback(() => { + const handleContinue = () => { onCopy(); addSuccessMessage('Copied to clipboard'); @@ -39,7 +39,7 @@ export function SplitInstallationIdModal(props: Props) { openAdminIntegrationTimeoutRef.current = window.setTimeout(() => { window.open('https://app.split.io/org/admin/integrations'); }, 2000); - }, [onCopy]); + }; // no need to translate this temporary component return ( diff --git a/static/app/views/settings/organizationRelay/relayWrapper.tsx b/static/app/views/settings/organizationRelay/relayWrapper.tsx index efdbc53e09a774..e9393c5a677dda 100644 --- a/static/app/views/settings/organizationRelay/relayWrapper.tsx +++ b/static/app/views/settings/organizationRelay/relayWrapper.tsx @@ -1,4 +1,4 @@ -import {useCallback, useState} from 'react'; +import {useState} from 'react'; import omit from 'lodash/omit'; import {z} from 'zod'; @@ -42,7 +42,7 @@ export function RelayWrapper() { const disabled = !organization.access.includes('org:write'); - const handleOpenAddDialog = useCallback(() => { + const handleOpenAddDialog = () => { openModal(modalProps => ( )); - }, [relays, api, organization.slug]); + }; return ( @@ -172,50 +172,44 @@ function RelayUsageList({ } ); - const handleOpenEditDialog = useCallback( - (publicKey: string) => { - const editRelay = relays.find(relay => relay.publicKey === publicKey); + const handleOpenEditDialog = (publicKey: string) => { + const editRelay = relays.find(relay => relay.publicKey === publicKey); - if (!editRelay) { - return; - } - - openModal(modalProps => ( - { - addSuccessMessage(t('Successfully updated Relay public key')); - onRelaysChange(response.trustedRelays); - }} - /> - )); - }, - [orgSlug, relays, api, onRelaysChange] - ); + if (!editRelay) { + return; + } - const handleDeleteRelay = useCallback( - async (publicKey: string) => { - const trustedRelays = relays - .filter(relay => relay.publicKey !== publicKey) - .map(relay => omit(relay, ['created', 'lastModified'])); - - try { - const response = await api.requestPromise(`/organizations/${orgSlug}/`, { - method: 'PUT', - data: {trustedRelays}, - }); - addSuccessMessage(t('Successfully deleted Relay public key')); - onRelaysChange(response.trustedRelays); - } catch { - addErrorMessage(t('An unknown error occurred while deleting Relay public key')); - } - }, - [relays, api, orgSlug, onRelaysChange] - ); + openModal(modalProps => ( + { + addSuccessMessage(t('Successfully updated Relay public key')); + onRelaysChange(response.trustedRelays); + }} + /> + )); + }; + + const handleDeleteRelay = async (publicKey: string) => { + const trustedRelays = relays + .filter(relay => relay.publicKey !== publicKey) + .map(relay => omit(relay, ['created', 'lastModified'])); + + try { + const response = await api.requestPromise(`/organizations/${orgSlug}/`, { + method: 'PUT', + data: {trustedRelays}, + }); + addSuccessMessage(t('Successfully deleted Relay public key')); + onRelaysChange(response.trustedRelays); + } catch { + addErrorMessage(t('An unknown error occurred while deleting Relay public key')); + } + }; if (isPending) { return ; diff --git a/static/app/views/settings/project/preprod/statusCheckRuleForm.tsx b/static/app/views/settings/project/preprod/statusCheckRuleForm.tsx index 6f774b6b63230e..44f41bdf81483a 100644 --- a/static/app/views/settings/project/preprod/statusCheckRuleForm.tsx +++ b/static/app/views/settings/project/preprod/statusCheckRuleForm.tsx @@ -1,4 +1,4 @@ -import {useCallback, useState} from 'react'; +import {useState} from 'react'; import styled from '@emotion/styled'; import {Button} from '@sentry/scraps/button'; @@ -65,9 +65,9 @@ export function StatusCheckRuleForm({rule, onSave, onDelete}: Props) { }); }; - const handleQueryChange = useCallback((query: string) => { + const handleQueryChange = (query: string) => { setFilterQuery(query); - }, []); + }; const handleDelete = () => { const ruleDisplayValue = diff --git a/static/app/views/settings/project/preprod/statusCheckRules.tsx b/static/app/views/settings/project/preprod/statusCheckRules.tsx index 6e9eeaf8b55474..ebcd69a984ca23 100644 --- a/static/app/views/settings/project/preprod/statusCheckRules.tsx +++ b/static/app/views/settings/project/preprod/statusCheckRules.tsx @@ -71,21 +71,18 @@ export function StatusCheckRules() { [location.query, navigate] ); - const handleToggleExpanded = useCallback( - (ruleId: string, isExpanded: boolean) => { - const newExpanded = new Set(expandedRuleIds); - if (isExpanded) { - newExpanded.add(ruleId); - } else { - newExpanded.delete(ruleId); - if (ruleId === newRuleId) { - setNewRuleId(null); - } + const handleToggleExpanded = (ruleId: string, isExpanded: boolean) => { + const newExpanded = new Set(expandedRuleIds); + if (isExpanded) { + newExpanded.add(ruleId); + } else { + newExpanded.delete(ruleId); + if (ruleId === newRuleId) { + setNewRuleId(null); } - updateExpandedInUrl([...newExpanded]); - }, - [expandedRuleIds, newRuleId, updateExpandedInUrl] - ); + } + updateExpandedInUrl([...newExpanded]); + }; const hasRepositories = !isLoadingRepos && repositories && repositories.length > 0; diff --git a/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx b/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx index 7e527f79d2ea12..48a2bb35100242 100644 --- a/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx +++ b/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useState} from 'react'; +import {Fragment, useState} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import iconAndroid from 'sentry-logos/logo-android.svg'; @@ -415,23 +415,20 @@ export function ProjectFiltersSettings({project, params, features}: Props) { const filterList = filterListData ?? []; - const handleLegacyChange = useCallback( - ({ - onChange, - onBlur, - event, - subfilters, - }: { - event: React.ChangeEvent | React.MouseEvent; - onBlur: FormFieldProps['onBlur']; - onChange: FormFieldProps['onChange']; - subfilters: Set; - }) => { - onChange?.(subfilters, event); - onBlur?.(subfilters, event); - }, - [] - ); + const handleLegacyChange = ({ + onChange, + onBlur, + event, + subfilters, + }: { + event: React.ChangeEvent | React.MouseEvent; + onBlur: FormFieldProps['onBlur']; + onChange: FormFieldProps['onChange']; + subfilters: Set; + }) => { + onChange?.(subfilters, event); + onBlur?.(subfilters, event); + }; if (isPending) { return ; diff --git a/static/app/views/settings/project/projectKeys/details/keySettings.tsx b/static/app/views/settings/project/projectKeys/details/keySettings.tsx index 58200b1ab90f14..23a4056de174c8 100644 --- a/static/app/views/settings/project/projectKeys/details/keySettings.tsx +++ b/static/app/views/settings/project/projectKeys/details/keySettings.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback} from 'react'; +import {Fragment} from 'react'; import {Button} from '@sentry/scraps/button'; @@ -51,7 +51,7 @@ export function KeySettings({ const {keyId, projectId} = params; const apiEndpoint = `/projects/${organization.slug}/${projectId}/keys/${keyId}/`; - const handleRemove = useCallback(async () => { + const handleRemove = async () => { addLoadingMessage(t('Revoking key\u2026')); try { @@ -67,7 +67,7 @@ export function KeySettings({ } catch (_err) { addErrorMessage(t('Unable to revoke key')); } - }, [organization, api, onRemove, keyId, projectId]); + }; const showOtlpTraces = organization.features.includes('relay-otlp-traces-endpoint'); const showOtlpLogs = organization.features.includes('relay-otel-logs-endpoint'); diff --git a/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx b/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx index 8cedf6fe164059..7cfd910e18e6ec 100644 --- a/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx +++ b/static/app/views/settings/project/projectKeys/details/loaderSettings.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useState} from 'react'; +import {Fragment, useState} from 'react'; import {ExternalLink} from '@sentry/scraps/link'; @@ -63,88 +63,74 @@ export function LoaderSettings({keyId, orgSlug, project, data, updateData}: Prop const apiEndpoint = `/projects/${orgSlug}/${project.slug}/keys/${keyId}/`; const loaderLink = data.dsn.cdn; - const updateLoaderOption = useCallback( - async (changes: { - browserSdkVersion?: string; - hasDebug?: boolean; - hasFeedback?: boolean; - hasLogsAndMetrics?: boolean; - hasPerformance?: boolean; - hasReplay?: boolean; - }) => { - setRequestPending(true); - setOptimisticState({ - browserSdkVersion: data.browserSdkVersion, - hasDebug: data.dynamicSdkLoaderOptions.hasDebug, - hasLogsAndMetrics: data.dynamicSdkLoaderOptions.hasLogsAndMetrics, - hasFeedback: data.dynamicSdkLoaderOptions.hasFeedback, - hasPerformance: data.dynamicSdkLoaderOptions.hasPerformance, - hasReplay: data.dynamicSdkLoaderOptions.hasReplay, - ...changes, - }); - addLoadingMessage(); + const updateLoaderOption = async (changes: { + browserSdkVersion?: string; + hasDebug?: boolean; + hasFeedback?: boolean; + hasLogsAndMetrics?: boolean; + hasPerformance?: boolean; + hasReplay?: boolean; + }) => { + setRequestPending(true); + setOptimisticState({ + browserSdkVersion: data.browserSdkVersion, + hasDebug: data.dynamicSdkLoaderOptions.hasDebug, + hasLogsAndMetrics: data.dynamicSdkLoaderOptions.hasLogsAndMetrics, + hasFeedback: data.dynamicSdkLoaderOptions.hasFeedback, + hasPerformance: data.dynamicSdkLoaderOptions.hasPerformance, + hasReplay: data.dynamicSdkLoaderOptions.hasReplay, + ...changes, + }); + addLoadingMessage(); - const browserSdkVersion = changes.browserSdkVersion ?? data.browserSdkVersion; + const browserSdkVersion = changes.browserSdkVersion ?? data.browserSdkVersion; - let payload: any; - if (sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)) { - payload = { - browserSdkVersion, - dynamicSdkLoaderOptions: { - hasDebug: changes.hasDebug ?? data.dynamicSdkLoaderOptions.hasDebug, - hasLogsAndMetrics: sdkVersionSupportsLogsAndMetrics(browserSdkVersion) - ? (changes.hasLogsAndMetrics ?? - data.dynamicSdkLoaderOptions.hasLogsAndMetrics) - : false, - hasFeedback: changes.hasFeedback ?? data.dynamicSdkLoaderOptions.hasFeedback, - hasPerformance: - changes.hasPerformance ?? data.dynamicSdkLoaderOptions.hasPerformance, - hasReplay: changes.hasReplay ?? data.dynamicSdkLoaderOptions.hasReplay, - }, - }; - } else { - payload = { - browserSdkVersion, - dynamicSdkLoaderOptions: { - hasDebug: changes.hasDebug ?? data.dynamicSdkLoaderOptions.hasDebug, - hasLogsAndMetrics: false, - hasFeedback: false, - hasPerformance: false, - hasReplay: false, - }, - }; - } + let payload: any; + if (sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)) { + payload = { + browserSdkVersion, + dynamicSdkLoaderOptions: { + hasDebug: changes.hasDebug ?? data.dynamicSdkLoaderOptions.hasDebug, + hasLogsAndMetrics: sdkVersionSupportsLogsAndMetrics(browserSdkVersion) + ? (changes.hasLogsAndMetrics ?? + data.dynamicSdkLoaderOptions.hasLogsAndMetrics) + : false, + hasFeedback: changes.hasFeedback ?? data.dynamicSdkLoaderOptions.hasFeedback, + hasPerformance: + changes.hasPerformance ?? data.dynamicSdkLoaderOptions.hasPerformance, + hasReplay: changes.hasReplay ?? data.dynamicSdkLoaderOptions.hasReplay, + }, + }; + } else { + payload = { + browserSdkVersion, + dynamicSdkLoaderOptions: { + hasDebug: changes.hasDebug ?? data.dynamicSdkLoaderOptions.hasDebug, + hasLogsAndMetrics: false, + hasFeedback: false, + hasPerformance: false, + hasReplay: false, + }, + }; + } - try { - const response = await api.requestPromise(apiEndpoint, { - method: 'PUT', - data: payload, - }); + try { + const response = await api.requestPromise(apiEndpoint, { + method: 'PUT', + data: payload, + }); - updateData(response); + updateData(response); - addSuccessMessage(t('Successfully updated dynamic SDK loader configuration')); - } catch (error) { - const message = t('Unable to updated dynamic SDK loader configuration'); - handleXhrErrorResponse(message, error as RequestError); - addErrorMessage(message); - } finally { - setRequestPending(false); - } - }, - [ - api, - apiEndpoint, - data.browserSdkVersion, - data.dynamicSdkLoaderOptions.hasDebug, - data.dynamicSdkLoaderOptions.hasLogsAndMetrics, - data.dynamicSdkLoaderOptions.hasFeedback, - data.dynamicSdkLoaderOptions.hasPerformance, - data.dynamicSdkLoaderOptions.hasReplay, - setRequestPending, - updateData, - ] - ); + addSuccessMessage(t('Successfully updated dynamic SDK loader configuration')); + } catch (error) { + const message = t('Unable to updated dynamic SDK loader configuration'); + handleXhrErrorResponse(message, error as RequestError); + addErrorMessage(message); + } finally { + setRequestPending(false); + } + }; return ( diff --git a/static/app/views/settings/projectSeer/autofixRepositories.tsx b/static/app/views/settings/projectSeer/autofixRepositories.tsx index 13598f786ad67a..fa578571cc4340 100644 --- a/static/app/views/settings/projectSeer/autofixRepositories.tsx +++ b/static/app/views/settings/projectSeer/autofixRepositories.tsx @@ -177,32 +177,26 @@ export function AutofixRepositories({project}: ProjectSeerProps) { [updatePreferences] ); - const removeRepository = useCallback( - (repoId: string) => { - setSelectedRepoIds(prevSelectedIds => { - const newIds = prevSelectedIds.filter(id => id !== repoId); - updatePreferences(newIds); - return newIds; - }); - }, - [updatePreferences] - ); - - const updateRepoSettings = useCallback( - (repoId: string, settings: RepoSettings) => { - setRepoSettings(prev => { - const newSettings = { - ...prev, - [repoId]: settings, - }; + const removeRepository = (repoId: string) => { + setSelectedRepoIds(prevSelectedIds => { + const newIds = prevSelectedIds.filter(id => id !== repoId); + updatePreferences(newIds); + return newIds; + }); + }; + + const updateRepoSettings = (repoId: string, settings: RepoSettings) => { + setRepoSettings(prev => { + const newSettings = { + ...prev, + [repoId]: settings, + }; - updatePreferences(undefined, newSettings); + updatePreferences(undefined, newSettings); - return newSettings; - }); - }, - [updatePreferences] - ); + return newSettings; + }); + }; const {unselectedRepositories, filteredSelectedRepositories} = useMemo(() => { if (!repositories || repositories.length === 0) { @@ -231,7 +225,7 @@ export function AutofixRepositories({project}: ProjectSeerProps) { const isRepoLimitReached = selectedRepoIds.length >= MAX_REPOS_LIMIT; - const openAddRepoModal = useCallback(() => { + const openAddRepoModal = () => { openModal(deps => ( )); - }, [selectedRepoIds, handleSaveModalSelections]); + }; return ( diff --git a/static/app/views/settings/seer/overview/codeReviewOverviewSection.tsx b/static/app/views/settings/seer/overview/codeReviewOverviewSection.tsx index bb218427bf5def..c55513ea86d40e 100644 --- a/static/app/views/settings/seer/overview/codeReviewOverviewSection.tsx +++ b/static/app/views/settings/seer/overview/codeReviewOverviewSection.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import {mutationOptions} from '@tanstack/react-query'; import uniqBy from 'lodash/uniqBy'; import {z} from 'zod'; @@ -20,8 +19,7 @@ import {t, tct, tn} from 'sentry/locale'; import {DEFAULT_CODE_REVIEW_TRIGGERS} from 'sentry/types/integrations'; import type {Organization} from 'sentry/types/organization'; import {useFetchAllPages} from 'sentry/utils/api/apiFetch'; -import {useInfiniteQuery, useQueryClient} from 'sentry/utils/queryClient'; -import {fetchMutation} from 'sentry/utils/queryClient'; +import {fetchMutation, useInfiniteQuery, useQueryClient} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; export function useCodeReviewOverviewSection() { @@ -103,71 +101,65 @@ export function CodeReviewOverviewSection({ }, }); - const handleToggleCodeReview = useCallback( - (enabledCodeReview: boolean) => { - const repositoryIds = ( - enabledCodeReview - ? seerRepos.filter(repo => !repo.settings?.enabledCodeReview) - : reposWithCodeReview - ).map(repo => repo.id); - mutateRepositorySettings( - {enabledCodeReview, repositoryIds}, - { - onError: (_, variables) => { - addErrorMessage( - tn( - 'Failed to update code review for %s repository', - 'Failed to update code review for %s repositories', - variables.repositoryIds.length - ) - ); - }, - onSuccess: (_, variables) => { - addSuccessMessage( - tn( - 'Code review updated for %s repository', - 'Code review updated for %s repositories', - variables.repositoryIds.length - ) - ); - }, - } - ); - }, - [mutateRepositorySettings, reposWithCodeReview, seerRepos] - ); + const handleToggleCodeReview = (enabledCodeReview: boolean) => { + const repositoryIds = ( + enabledCodeReview + ? seerRepos.filter(repo => !repo.settings?.enabledCodeReview) + : reposWithCodeReview + ).map(repo => repo.id); + mutateRepositorySettings( + {enabledCodeReview, repositoryIds}, + { + onError: (_, variables) => { + addErrorMessage( + tn( + 'Failed to update code review for %s repository', + 'Failed to update code review for %s repositories', + variables.repositoryIds.length + ) + ); + }, + onSuccess: (_, variables) => { + addSuccessMessage( + tn( + 'Code review updated for %s repository', + 'Code review updated for %s repositories', + variables.repositoryIds.length + ) + ); + }, + } + ); + }; - const handleChangeTriggers = useCallback( - (newTriggers: string[]) => { - mutateRepositorySettings( - { - codeReviewTriggers: newTriggers, - repositoryIds: seerRepos.map(repo => repo.id), + const handleChangeTriggers = (newTriggers: string[]) => { + mutateRepositorySettings( + { + codeReviewTriggers: newTriggers, + repositoryIds: seerRepos.map(repo => repo.id), + }, + { + onError: (_, variables) => { + addErrorMessage( + tn( + 'Failed to update triggers for %s repository', + 'Failed to update triggers for %s repositories', + variables.repositoryIds.length + ) + ); }, - { - onError: (_, variables) => { - addErrorMessage( - tn( - 'Failed to update triggers for %s repository', - 'Failed to update triggers for %s repositories', - variables.repositoryIds.length - ) - ); - }, - onSuccess: (_, variables) => { - addSuccessMessage( - tn( - 'Triggers updated for %s repository', - 'Triggers updated for %s repositories', - variables.repositoryIds.length - ) - ); - }, - } - ); - }, - [mutateRepositorySettings, seerRepos] - ); + onSuccess: (_, variables) => { + addSuccessMessage( + tn( + 'Triggers updated for %s repository', + 'Triggers updated for %s repositories', + variables.repositoryIds.length + ) + ); + }, + } + ); + }; return ( { - event.preventDefault(); - if (!isFormValid || !selectedOrg || !selectedProjectId) { - return; - } - - let projectId = selectedProjectId; - try { - if (isCreateProjectSelected) { - const project = await createProjectMutation.mutateAsync({ - organization: selectedOrg, - team: newProjectTeam, - name: newProjectName, - platform: newProjectPlatform || platformParam || 'other', - }); - - projectId = project.id; - } - } catch { - addErrorMessage('Failed to create project! Please try again'); - return; - } + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + if (!isFormValid || !selectedOrg || !selectedProjectId) { + return; + } - try { - await updateWizardCacheMutation.mutateAsync({ - organizationId: selectedOrg.id, - projectId, + let projectId = selectedProjectId; + try { + if (isCreateProjectSelected) { + const project = await createProjectMutation.mutateAsync({ + organization: selectedOrg, + team: newProjectTeam, + name: newProjectName, + platform: newProjectPlatform || platformParam || 'other', }); - } catch (e) { - const errorMessage = errorIsHasNoDsnError(e) - ? t( - 'The selected project has no active DSN. Please add an active DSN to the project.' - ) - : t('Something went wrong! Please try again.'); - - addErrorMessage(errorMessage); + + projectId = project.id; } - }, - [ - isFormValid, - selectedOrg, - selectedProjectId, - isCreateProjectSelected, - createProjectMutation, - newProjectTeam, - newProjectName, - newProjectPlatform, - updateWizardCacheMutation, - ] - ); + } catch { + addErrorMessage('Failed to create project! Please try again'); + return; + } + + try { + await updateWizardCacheMutation.mutateAsync({ + organizationId: selectedOrg.id, + projectId, + }); + } catch (e) { + const errorMessage = errorIsHasNoDsnError(e) + ? t( + 'The selected project has no active DSN. Please add an active DSN to the project.' + ) + : t('Something went wrong! Please try again.'); + + addErrorMessage(errorMessage); + } + }; if (isSuccess) { return ; diff --git a/static/gsAdmin/views/broadcasts.tsx b/static/gsAdmin/views/broadcasts.tsx index 5b1fb3dfcdf3dd..6bcee4a37e147a 100644 --- a/static/gsAdmin/views/broadcasts.tsx +++ b/static/gsAdmin/views/broadcasts.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import moment from 'moment-timezone'; import {Button} from '@sentry/scraps/button'; @@ -40,11 +39,11 @@ export function Broadcasts() { const hasPermission = ConfigStore.get('user').permissions.has('broadcasts.admin'); const fields = getBroadcastSchema(); - const handleNewBroadcast = useCallback(() => { + const handleNewBroadcast = () => { openModal(deps => , { closeEvents: 'escape-key', }); - }, [fields]); + }; return (
diff --git a/static/gsApp/components/productUnavailableCTA.tsx b/static/gsApp/components/productUnavailableCTA.tsx index db779552fc36c5..6d875b8b442a0a 100644 --- a/static/gsApp/components/productUnavailableCTA.tsx +++ b/static/gsApp/components/productUnavailableCTA.tsx @@ -1,4 +1,4 @@ -import {useCallback, useEffect, useMemo, useState} from 'react'; +import {useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import {Alert} from '@sentry/scraps/alert'; @@ -103,7 +103,7 @@ function RequestUpdateAlert({ ); }, [analyticsCommonProps]); - const handleClick = useCallback(async () => { + const handleClick = async () => { setLoading(true); trackGetsentryAnalytics( @@ -127,7 +127,7 @@ function RequestUpdateAlert({ } setLoading(false); - }, [api, isAncientPlan, organization, analyticsCommonProps]); + }; return ( { + const handleClick = () => { trackGetsentryAnalytics( 'product_unavailable_upsell_alert_button.clicked', analyticsCommonProps @@ -205,7 +205,7 @@ function UpdatePlanAlert({ } am2UpsellModal.showModal(); - }, [analyticsCommonProps, canSelfServe, isAncientPlan, am2UpsellModal]); + }; return ( { + const onUpdatePlan = async () => { try { await api.requestPromise(`/customers/${organization.slug}/subscription/`, { method: 'PUT', @@ -224,9 +224,9 @@ function ActionButtons({ Sentry.captureException(err); redirectToManage(organization); } - }, [api, organization, subscription, plan, reservations, onComplete, hasPriceChange]); + }; - const onClickManageSubscription = useCallback(() => { + const onClickManageSubscription = () => { trackGetsentryAnalytics('upgrade_now.modal.manage_sub', { organization, surface: 'profiling', @@ -235,7 +235,7 @@ function ActionButtons({ channel: subscription.channel, has_billing_scope: organization.access?.includes('org:billing'), }); - }, [organization, subscription]); + }; const hasBillingAccess = organization.access?.includes('org:billing'); diff --git a/static/gsApp/components/replayOnboardingCTA.tsx b/static/gsApp/components/replayOnboardingCTA.tsx index 650ad1f353d465..1137f6f0f1d079 100644 --- a/static/gsApp/components/replayOnboardingCTA.tsx +++ b/static/gsApp/components/replayOnboardingCTA.tsx @@ -1,5 +1,5 @@ import type {ReactNode} from 'react'; -import {Fragment, useCallback, useEffect, useState} from 'react'; +import {Fragment, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import {Button, LinkButton} from '@sentry/scraps/button'; @@ -55,7 +55,7 @@ function ReplayOnboardingCTAUpsell({ }); }, [organization, subscription]); - const onEmailOwner = useCallback(async () => { + const onEmailOwner = async () => { await sendReplayOnboardRequest({ orgSlug: organization.slug, api, @@ -73,7 +73,7 @@ function ReplayOnboardingCTAUpsell({ }); }, }); - }, [api, organization, subscription, dismiss]); + }; const [didClickOpenModal, setDidClickOpenModal] = useState(); const previewData = usePreviewData({ @@ -82,9 +82,9 @@ function ReplayOnboardingCTAUpsell({ enabled: !subscription.canSelfServe || !hasBillingAccess, }); - const handleOpenModal = useCallback(() => { + const handleOpenModal = () => { setDidClickOpenModal(true); - }, []); + }; // Once we have 1) previewData, and 2) the user clicked the button; then open the modal useEffect(() => { @@ -146,7 +146,7 @@ function ReplayOnboardingCTAUpsell({ subscription, ]); - const onClickManageSubscription = useCallback(() => { + const onClickManageSubscription = () => { trackGetsentryAnalytics('replay.list_page.manage_sub', { organization, surface: 'replay_onboarding_banner', @@ -155,7 +155,7 @@ function ReplayOnboardingCTAUpsell({ channel: subscription.channel, has_billing_scope: organization.access?.includes('org:billing'), }); - }, [organization, subscription]); + }; if (!subscription.canSelfServe) { // Two cases: diff --git a/static/gsApp/components/upgradeNowModal/actionButtons.tsx b/static/gsApp/components/upgradeNowModal/actionButtons.tsx index 951355c21aef84..279fa09abe1f20 100644 --- a/static/gsApp/components/upgradeNowModal/actionButtons.tsx +++ b/static/gsApp/components/upgradeNowModal/actionButtons.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; @@ -47,7 +46,7 @@ export function ActionButtons({ }: Props) { const api = useApi(); - const onUpdatePlan = useCallback(async () => { + const onUpdatePlan = async () => { try { await api.requestPromise(`/customers/${organization.slug}/subscription/`, { method: 'PUT', @@ -82,18 +81,9 @@ export function ActionButtons({ Sentry.captureException(err); redirectToManage(organization); } - }, [ - api, - onComplete, - organization, - plan, - previewData.billedAmount, - reservations, - subscription, - surface, - ]); - - const onEmailOwner = useCallback(async () => { + }; + + const onEmailOwner = async () => { const currentPlanName = subscription.planTier === PlanTier.AM2 ? 'am2-non-beta' : 'am1-non-beta'; @@ -117,9 +107,9 @@ export function ActionButtons({ redirectToManage(organization); }, }); - }, [api, organization, subscription, surface, onComplete]); + }; - const onClickManageSubscription = useCallback(() => { + const onClickManageSubscription = () => { trackGetsentryAnalytics('upgrade_now.modal.manage_sub', { organization, surface, @@ -128,7 +118,7 @@ export function ActionButtons({ channel: subscription.channel, has_billing_scope: organization.access?.includes('org:billing'), }); - }, [organization, subscription, surface]); + }; const hasBillingAccess = organization.access?.includes('org:billing'); diff --git a/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx b/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx index ae855f7c77cc74..bd86f9b66c0ba7 100644 --- a/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx +++ b/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx @@ -1,4 +1,3 @@ -import {useCallback} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; @@ -44,7 +43,7 @@ function UpgradeNowModal({ const api = useApi(); - const onUpdatePlan = useCallback(async () => { + const onUpdatePlan = async () => { try { await api.requestPromise(`/customers/${organization.slug}/subscription/`, { method: 'PUT', @@ -82,7 +81,7 @@ function UpgradeNowModal({ ) ); } - }, [api, organization, subscription, plan, reservations, onComplete, surface]); + }; return ( diff --git a/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx b/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx index 5da438b53e427a..e8235417608f1d 100644 --- a/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx +++ b/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx @@ -1,5 +1,3 @@ -import {useCallback} from 'react'; - import {Flex} from '@sentry/scraps/layout'; import {t} from 'sentry/locale'; @@ -25,27 +23,24 @@ export function SetSpendLimit({ subscription, checkoutTier, }: StepProps) { - const handleBudgetChange = useCallback( - ({onDemandBudgets}: {onDemandBudgets: OnDemandBudgets}) => { - const totalBudget = getTotalBudget(onDemandBudgets); - onUpdate({ - ...formData, - onDemandBudget: onDemandBudgets, - onDemandMaxSpend: totalBudget, - }); + const handleBudgetChange = ({onDemandBudgets}: {onDemandBudgets: OnDemandBudgets}) => { + const totalBudget = getTotalBudget(onDemandBudgets); + onUpdate({ + ...formData, + onDemandBudget: onDemandBudgets, + onDemandMaxSpend: totalBudget, + }); - if (organization) { - trackGetsentryAnalytics('checkout.payg_changed', { - organization, - subscription, - plan: formData.plan, - cents: totalBudget || 0, - method: 'textbox', - }); - } - }, - [onUpdate, organization, subscription, formData] - ); + if (organization) { + trackGetsentryAnalytics('checkout.payg_changed', { + organization, + subscription, + plan: formData.plan, + cents: totalBudget || 0, + method: 'textbox', + }); + } + }; return ( diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesItem.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesItem.tsx index 2a76b6de72a7bb..24d3d724dad4d8 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesItem.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesItem.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useState} from 'react'; +import {Fragment, useState} from 'react'; import styled from '@emotion/styled'; import {Button} from '@sentry/scraps/button'; @@ -60,43 +60,37 @@ export function AutofixRepositoriesItem({ repository.branch_overrides || [] ); - const handleUpdateOverride = useCallback( - (idx: number, updatedOverride: BranchOverride) => { - const newLocalOverrides = localOverrides.toSpliced(idx, 1, updatedOverride); - setLocalOverrides(newLocalOverrides); - - // Only sync valid overrides to the server if they changed - const branchOverrides = newLocalOverrides.filter(isOverrideValid); - if (!areOverridesEqual(branchOverrides, repository.branch_overrides || [])) { - onUpdateRepo({ - ...repository, - branch_overrides: branchOverrides, - }); - } - }, - [localOverrides, repository, onUpdateRepo] - ); - - const handleRemoveOverride = useCallback( - (idx: number) => { - const newLocalOverrides = localOverrides.toSpliced(idx, 1); - setLocalOverrides(newLocalOverrides); - - // Sync valid overrides to the server if they changed - const branchOverrides = newLocalOverrides.filter(isOverrideValid); - if (!areOverridesEqual(branchOverrides, repository.branch_overrides || [])) { - onUpdateRepo({ - ...repository, - branch_overrides: branchOverrides, - }); - } - }, - [localOverrides, repository, onUpdateRepo] - ); - - const handleAddOverride = useCallback(() => { + const handleUpdateOverride = (idx: number, updatedOverride: BranchOverride) => { + const newLocalOverrides = localOverrides.toSpliced(idx, 1, updatedOverride); + setLocalOverrides(newLocalOverrides); + + // Only sync valid overrides to the server if they changed + const branchOverrides = newLocalOverrides.filter(isOverrideValid); + if (!areOverridesEqual(branchOverrides, repository.branch_overrides || [])) { + onUpdateRepo({ + ...repository, + branch_overrides: branchOverrides, + }); + } + }; + + const handleRemoveOverride = (idx: number) => { + const newLocalOverrides = localOverrides.toSpliced(idx, 1); + setLocalOverrides(newLocalOverrides); + + // Sync valid overrides to the server if they changed + const branchOverrides = newLocalOverrides.filter(isOverrideValid); + if (!areOverridesEqual(branchOverrides, repository.branch_overrides || [])) { + onUpdateRepo({ + ...repository, + branch_overrides: branchOverrides, + }); + } + }; + + const handleAddOverride = () => { setLocalOverrides([...localOverrides, {...DEFAULT_OVERRIDE}]); - }, [localOverrides]); + }; return ( diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesList.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesList.tsx index 213f8b381131a4..6fa554ffe3b775 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesList.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/autofixRepositoriesList.tsx @@ -1,4 +1,4 @@ -import {useCallback, useMemo} from 'react'; +import {useMemo} from 'react'; import styled from '@emotion/styled'; import seerConfigBug1 from 'getsentry-images/spot/seer-config-bug-1.svg'; @@ -70,27 +70,24 @@ export function AutofixRepositories({canWrite, preference, project}: Props) { [preference] ); - const handleSaveRepoList = useCallback( - (updatedRepositories: SeerRepoDefinition[]) => { - updateProjectSeerPreferences( - { - repositories: updatedRepositories, - automated_run_stopping_point: preference?.automated_run_stopping_point, - automation_handoff: preference?.automation_handoff, - }, - { - onError: () => addErrorMessage(t('Failed to connect repositories')), - onSuccess: () => - addSuccessMessage( - t('%s repo(s) connected to %s', updatedRepositories.length, project.slug) - ), - } - ); - }, - [updateProjectSeerPreferences, preference, project.slug] - ); + const handleSaveRepoList = (updatedRepositories: SeerRepoDefinition[]) => { + updateProjectSeerPreferences( + { + repositories: updatedRepositories, + automated_run_stopping_point: preference?.automated_run_stopping_point, + automation_handoff: preference?.automation_handoff, + }, + { + onError: () => addErrorMessage(t('Failed to connect repositories')), + onSuccess: () => + addSuccessMessage( + t('%s repo(s) connected to %s', updatedRepositories.length, project.slug) + ), + } + ); + }; - const handleAddRepoClick = useCallback(() => { + const handleAddRepoClick = () => { openModal(deps => ( )); - }, [repoMap, handleSaveRepoList, repositories, organization.id]); + }; if (isFetchingRepositories) { return ; diff --git a/static/gsApp/views/seerAutomation/onboarding/configureCodeReviewStep.tsx b/static/gsApp/views/seerAutomation/onboarding/configureCodeReviewStep.tsx index b4151e769be8f0..4dbe489d6eab4f 100644 --- a/static/gsApp/views/seerAutomation/onboarding/configureCodeReviewStep.tsx +++ b/static/gsApp/views/seerAutomation/onboarding/configureCodeReviewStep.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback} from 'react'; +import {Fragment} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; @@ -42,7 +42,7 @@ export function ConfigureCodeReviewStep() { const {mutate: updateRepositorySettings, isPending: isUpdateRepositorySettingsPending} = useBulkUpdateRepositorySettings(); - const handleNextStep = useCallback(() => { + const handleNextStep = () => { const existingRepostoriesToRemove = unselectedCodeReviewRepositories .filter(repo => repo.settings?.enabledCodeReview) .map(repo => repo.id); @@ -134,15 +134,7 @@ export function ConfigureCodeReviewStep() { t('Failed to update AI Code Review settings, reload and try again') ); }); - }, [ - clearRootCauseAnalysisRepositories, - selectedCodeReviewRepositories, - unselectedCodeReviewRepositories, - currentStep, - setCurrentStep, - updateRepositorySettings, - organization, - ]); + }; return ( diff --git a/static/gsApp/views/seerAutomation/onboarding/configureDefaultsStep.tsx b/static/gsApp/views/seerAutomation/onboarding/configureDefaultsStep.tsx index d3057546eee824..b3eb9ad600cad7 100644 --- a/static/gsApp/views/seerAutomation/onboarding/configureDefaultsStep.tsx +++ b/static/gsApp/views/seerAutomation/onboarding/configureDefaultsStep.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useState} from 'react'; +import {Fragment, useState} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; @@ -49,11 +49,11 @@ export function ConfigureDefaultsStep() { const {mutate: updateOrganization, isPending: isUpdateOrganizationPending} = useUpdateOrganization(organization); - const handlePreviousStep = useCallback(() => { + const handlePreviousStep = () => { setCurrentStep(currentStep - 1); - }, [setCurrentStep, currentStep]); + }; - const handleNextStep = useCallback(() => { + const handleNextStep = () => { updateOrganization( { defaultAutofixAutomationTuning: proposeFixesEnabled ? 'medium' : 'off', @@ -82,15 +82,7 @@ export function ConfigureDefaultsStep() { }, } ); - }, [ - autoCreatePREnabled, - enableCodeReview, - proposeFixesEnabled, - updateOrganization, - currentStep, - setCurrentStep, - organization, - ]); + }; return ( diff --git a/static/gsApp/views/seerAutomation/onboarding/configureRootCauseAnalysisStep.tsx b/static/gsApp/views/seerAutomation/onboarding/configureRootCauseAnalysisStep.tsx index 6c95324d778aa3..7b406ecde1b33d 100644 --- a/static/gsApp/views/seerAutomation/onboarding/configureRootCauseAnalysisStep.tsx +++ b/static/gsApp/views/seerAutomation/onboarding/configureRootCauseAnalysisStep.tsx @@ -88,11 +88,11 @@ export function ConfigureRootCauseAnalysisStep() { addRepositoryProjectMappings, ]); - const handlePreviousStep = useCallback(() => { + const handlePreviousStep = () => { setCurrentStep(currentStep - 1); - }, [setCurrentStep, currentStep]); + }; - const handleNextStep = useCallback(() => { + const handleNextStep = () => { // Build a map from repo ID to full repo object const repoMap = new Map( selectedRootCauseAnalysisRepositories.map(repo => [repo.id, repo]) @@ -162,16 +162,7 @@ export function ConfigureRootCauseAnalysisStep() { }, } ); - }, [ - setCurrentStep, - currentStep, - updateAutofix, - repositoryProjectMapping, - selectedRootCauseAnalysisRepositories, - autoCreatePREnabled, - organization, - setAutoCreatePR, - ]); + }; const handleRepositoryProjectMappingsChange = useCallback( (repoId: string, index: number, newValue: string | undefined) => { @@ -190,12 +181,9 @@ export function ConfigureRootCauseAnalysisStep() { [changeRepositoryProjectMapping, repositoryProjectMapping] ); - const handleAutoCreatePRChange = useCallback( - (e: React.ChangeEvent) => { - setAutoCreatePREnabled(e.target.checked); - }, - [setAutoCreatePREnabled] - ); + const handleAutoCreatePRChange = (e: React.ChangeEvent) => { + setAutoCreatePREnabled(e.target.checked); + }; const availableRepositories = useMemo(() => { return ( diff --git a/static/gsApp/views/seerAutomation/onboarding/onboardingLegacy.tsx b/static/gsApp/views/seerAutomation/onboarding/onboardingLegacy.tsx index 2e528aa18bf0af..dd6c756fa02341 100644 --- a/static/gsApp/views/seerAutomation/onboarding/onboardingLegacy.tsx +++ b/static/gsApp/views/seerAutomation/onboarding/onboardingLegacy.tsx @@ -438,7 +438,7 @@ function AutoTriggerFixesButton({ const {setCurrentStep, getStepNumber} = useGuidedStepsContext(); const [isLoading, setIsLoading] = useState(false); - const handleEnableAutoTriggerFixes = useCallback(async () => { + const handleEnableAutoTriggerFixes = async () => { if (projectsWithRepos.length === 0) { addErrorMessage(t('No projects with repositories found to update')); return; @@ -491,15 +491,7 @@ function AutoTriggerFixesButton({ } finally { setIsLoading(false); } - }, [ - api, - organization.slug, - projectsWithRepos, - selectedThreshold, - queryClient, - getStepNumber, - setCurrentStep, - ]); + }; return (