diff --git a/static/app/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings.ts b/static/app/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings.ts index 994eb8fffa03d6..bddd42b62e928b 100644 --- a/static/app/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings.ts +++ b/static/app/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings.ts @@ -1,5 +1,6 @@ -import {useCallback} from 'react'; +import {useCallback, useMemo} from 'react'; +import type {ProjectSeerPreferences} from 'sentry/components/events/autofix/types'; import useFetchSequentialPages from 'sentry/utils/api/useFetchSequentialPages'; import { fetchMutation, @@ -8,6 +9,7 @@ import { type UseMutationOptions, } from 'sentry/utils/queryClient'; import useOrganization from 'sentry/utils/useOrganization'; +import useProjects from 'sentry/utils/useProjects'; type AutofixAutomationTuning = | 'off' @@ -18,13 +20,6 @@ type AutofixAutomationTuning = | 'always' // deprecated | null; // deprecated -type AutomatedRunStoppingPoint = - | 'root_cause' - | 'solution' - | 'code_changes' - | 'open_pr' - | 'background_agent'; - // Mirrors the backend SeerRepoDefinition type export interface BackendRepository { external_id: string; @@ -46,7 +41,8 @@ export interface BackendRepository { export type AutofixAutomationSettings = { autofixAutomationTuning: AutofixAutomationTuning; - automatedRunStoppingPoint: AutomatedRunStoppingPoint; + automatedRunStoppingPoint: ProjectSeerPreferences['automated_run_stopping_point']; + automationHandoff: ProjectSeerPreferences['automation_handoff']; projectId: string; reposCount: number; }; @@ -80,18 +76,20 @@ type AutofixAutomationUpdate = | { autofixAutomationTuning: AutofixAutomationTuning; projectIds: string[]; - automatedRunStoppingPoint?: never | AutomatedRunStoppingPoint; + automatedRunStoppingPoint?: + | never + | ProjectSeerPreferences['automated_run_stopping_point']; projectRepoMappings?: never | Record; } | { - automatedRunStoppingPoint: AutomatedRunStoppingPoint; + automatedRunStoppingPoint: ProjectSeerPreferences['automated_run_stopping_point']; projectIds: string[]; autofixAutomationTuning?: never | AutofixAutomationTuning; projectRepoMappings?: never | Record; } | { autofixAutomationTuning: AutofixAutomationTuning; - automatedRunStoppingPoint: AutomatedRunStoppingPoint; + automatedRunStoppingPoint: ProjectSeerPreferences['automated_run_stopping_point']; projectIds: string[]; projectRepoMappings?: never | Record; } @@ -99,7 +97,9 @@ type AutofixAutomationUpdate = projectIds: string[]; projectRepoMappings: Record; autofixAutomationTuning?: never | AutofixAutomationTuning; - automatedRunStoppingPoint?: never | AutomatedRunStoppingPoint; + automatedRunStoppingPoint?: + | never + | ProjectSeerPreferences['automated_run_stopping_point']; }; export function useUpdateBulkAutofixAutomationSettings( @@ -111,6 +111,12 @@ export function useUpdateBulkAutofixAutomationSettings( const organization = useOrganization(); const queryClient = useQueryClient(); + const {projects} = useProjects(); + const projectsById = useMemo( + () => new Map(projects.map(project => [project.id, project])), + [projects] + ); + return useMutation({ mutationFn: (data: AutofixAutomationUpdate) => { return fetchMutation({ @@ -124,6 +130,22 @@ export function useUpdateBulkAutofixAutomationSettings( queryClient.invalidateQueries({ queryKey: [`/organizations/${organization.slug}/autofix/automation-settings/`], }); + const [, , data] = args; + data.projectIds.forEach(projectId => { + const project = projectsById.get(projectId); + if (!project) { + return; + } + // Invalidate the query for ProjectOptions to Settings>Project>Seer details page + queryClient.invalidateQueries({ + queryKey: [`/projects/${organization.slug}/${project.slug}/`], + }); + // Invalidate the query for SeerPreferences to Settings>Project>Seer details page + queryClient.invalidateQueries({ + queryKey: [`/projects/${organization.slug}/${project.slug}/seer/preferences/`], + }); + }); + options?.onSettled?.(...args); }, }); diff --git a/static/app/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences.ts b/static/app/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences.ts index 2e9b80a03fec88..86d05199980fe3 100644 --- a/static/app/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences.ts +++ b/static/app/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences.ts @@ -68,6 +68,9 @@ export function useUpdateProjectSeerPreferences(project: Project) { }, onSettled: () => { queryClient.invalidateQueries({queryKey}); + queryClient.invalidateQueries({ + queryKey: [`/organizations/${organization.slug}/autofix/automation-settings/`], + }); }, }); } diff --git a/static/app/components/events/autofix/types.ts b/static/app/components/events/autofix/types.ts index 7f4906b786e56e..e7531c5f99f6a1 100644 --- a/static/app/components/events/autofix/types.ts +++ b/static/app/components/events/autofix/types.ts @@ -320,12 +320,7 @@ interface SeerAutomationHandoffConfiguration { export interface ProjectSeerPreferences { repositories: SeerRepoDefinition[]; - automated_run_stopping_point?: - | 'root_cause' - | 'solution' - | 'code_changes' - | 'open_pr' - | 'background_agent'; + automated_run_stopping_point?: 'root_cause' | 'solution' | 'code_changes' | 'open_pr'; automation_handoff?: SeerAutomationHandoffConfiguration; } diff --git a/static/app/views/settings/projectSeer/autofixRepositories.tsx b/static/app/views/settings/projectSeer/autofixRepositories.tsx index f97acd2a0e7d29..702b69479c427a 100644 --- a/static/app/views/settings/projectSeer/autofixRepositories.tsx +++ b/static/app/views/settings/projectSeer/autofixRepositories.tsx @@ -11,7 +11,10 @@ import {DropdownMenu} from 'sentry/components/dropdownMenu'; import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories'; import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences'; import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences'; -import type {RepoSettings} from 'sentry/components/events/autofix/types'; +import type { + ProjectSeerPreferences, + RepoSettings, +} from 'sentry/components/events/autofix/types'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Panel from 'sentry/components/panels/panel'; import PanelHeader from 'sentry/components/panels/panelHeader'; @@ -46,19 +49,16 @@ export function AutofixRepositories({project}: ProjectSeerProps) { const [repoSettings, setRepoSettings] = useState>({}); const [showSaveNotice, setShowSaveNotice] = useState(false); - const getDefaultStoppingPoint = useCallback((): - | 'root_cause' - | 'solution' - | 'code_changes' - | 'open_pr' => { - if (organization.features.includes('seat-based-seer-enabled')) { - return organization.autoOpenPrs ? 'open_pr' : 'code_changes'; - } - return 'root_cause'; - }, [organization.features, organization.autoOpenPrs]); + const getDefaultStoppingPoint = + useCallback((): ProjectSeerPreferences['automated_run_stopping_point'] => { + if (organization.features.includes('seat-based-seer-enabled')) { + return organization.autoOpenPrs ? 'open_pr' : 'code_changes'; + } + return 'root_cause'; + }, [organization.features, organization.autoOpenPrs]); const [automatedRunStoppingPoint, setAutomatedRunStoppingPoint] = useState< - 'root_cause' | 'solution' | 'code_changes' | 'open_pr' | 'background_agent' + ProjectSeerPreferences['automated_run_stopping_point'] >(getDefaultStoppingPoint()); useEffect(() => { diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/autoTriggeredFixesToggle.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/autoTriggeredFixesToggle.tsx index 8b67c5e8564f2a..e3e89027d4d122 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/autoTriggeredFixesToggle.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/autoTriggeredFixesToggle.tsx @@ -16,7 +16,7 @@ interface Props { export default function AutoTriggeredFixesToggle({canWrite, project}: Props) { const {mutate: updateProject} = useUpdateProject(project); - const isEnabled = Boolean( + const isAutoFixEnabled = Boolean( project.autofixAutomationTuning && project.autofixAutomationTuning !== 'off' ); @@ -28,11 +28,10 @@ export default function AutoTriggeredFixesToggle({canWrite, project}: Props) { help={t( 'Automatically analyze highly actionable issues, and create a root cause analysis without a user needing to prompt it.' )} - value={isEnabled} + value={isAutoFixEnabled} onChange={value => { - const newValue: Project['autofixAutomationTuning'] = value ? 'medium' : 'off'; updateProject( - {autofixAutomationTuning: newValue}, + {autofixAutomationTuning: value ? 'medium' : 'off'}, { onSuccess: () => addSuccessMessage( diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/backgroundAgentFields.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/backgroundAgentFields.tsx index 66f3df4872307a..719a0746c5d153 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/backgroundAgentFields.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/backgroundAgentFields.tsx @@ -48,16 +48,15 @@ function CursorIntegrationFields({ }: Props) { const {mutate: updateProjectSeerPreferences} = useUpdateProjectSeerPreferences(project); - const isBackgroundAgentEnabled = Boolean(preference?.automation_handoff); - const isAutoTriggeredFixesEnabled = Boolean( + const isAutoFixEnabled = Boolean( project.autofixAutomationTuning && project.autofixAutomationTuning !== 'off' ); + const isBackgroundAgentEnabled = Boolean(preference?.automation_handoff); - const isDisabled = - !canWrite || !isAutoTriggeredFixesEnabled || !isBackgroundAgentEnabled; + const isDisabled = !canWrite || !isAutoFixEnabled || !isBackgroundAgentEnabled; let disabledReason: string | null = null; - if (!isAutoTriggeredFixesEnabled) { + if (!isAutoFixEnabled) { disabledReason = t('Turn on Auto-Triggered Fixes to use this feature.'); } else if (!isBackgroundAgentEnabled) { disabledReason = t('This setting is only available when using background agents.'); diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/seerAgentSection.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/seerAgentSection.tsx index c40942af81b5fe..4cc89a20801a0d 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/seerAgentSection.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/seerAgentSection.tsx @@ -20,21 +20,19 @@ interface Props { export default function SeerAgentSection({canWrite, project, preference}: Props) { const {mutate: updateProjectSeerPreferences} = useUpdateProjectSeerPreferences(project); - const isAutoCreatePREnabled = Boolean( + const isAutoFixEnabled = Boolean( + project.autofixAutomationTuning && project.autofixAutomationTuning !== 'off' + ); + const isCreatePrEnabled = Boolean( preference?.automated_run_stopping_point && preference.automated_run_stopping_point !== 'code_changes' ); - const isBackgroundAgentEnabled = Boolean(preference?.automation_handoff); - const isAutoTriggeredFixesEnabled = Boolean( - project.autofixAutomationTuning && project.autofixAutomationTuning !== 'off' - ); - const isDisabled = - !canWrite || !isAutoTriggeredFixesEnabled || isBackgroundAgentEnabled; + const isDisabled = !canWrite || !isAutoFixEnabled || isBackgroundAgentEnabled; let disabledReason: string | null = null; - if (!isAutoTriggeredFixesEnabled) { + if (!isAutoFixEnabled) { disabledReason = t('Turn on Auto-Triggered Fixes to use this feature.'); } else if (isBackgroundAgentEnabled) { disabledReason = t('This setting is not available when using background agents.'); @@ -52,7 +50,7 @@ export default function SeerAgentSection({canWrite, project, preference}: Props) docsLink: , } )} - value={isAutoCreatePREnabled} + value={isCreatePrEnabled} onChange={value => { const newValue: ProjectSeerPreferences['automated_run_stopping_point'] = value ? 'open_pr' diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx index 6ce00ffa03b8b1..171ed729fb02a8 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx @@ -15,32 +15,27 @@ import LoadingIndicator from 'sentry/components/loadingIndicator'; import {SimpleTable} from 'sentry/components/tables/simpleTable'; import {IconSearch} from 'sentry/icons/iconSearch'; import {t, tct} from 'sentry/locale'; -import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import type {Sort} from 'sentry/utils/discover/fields'; import {ListItemCheckboxProvider} from 'sentry/utils/list/useListItemCheckboxState'; import type {ApiQueryKey} from 'sentry/utils/queryClient'; import {parseAsSort} from 'sentry/utils/queryString'; -import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import ProjectTableHeader from 'getsentry/views/seerAutomation/components/projectTable/seerProjectTableHeader'; import SeerProjectTableRow from 'getsentry/views/seerAutomation/components/projectTable/seerProjectTableRow'; -function getDefaultAutofixSettings( - organization: Organization, - projectId: string -): AutofixAutomationSettings { +function getDefaultAutofixSettings(projectId: string): AutofixAutomationSettings { return { - autofixAutomationTuning: organization.defaultAutofixAutomationTuning ?? 'off', - automatedRunStoppingPoint: organization.autoOpenPrs ? 'open_pr' : 'code_changes', + autofixAutomationTuning: 'off', + automatedRunStoppingPoint: 'code_changes', + automationHandoff: undefined, projectId, reposCount: 0, }; } export default function SeerProjectTable() { - const organization = useOrganization(); const {projects, fetching, fetchError} = useProjects(); const {pages: autofixAutomationSettings, isFetching: isFetchingSettings} = @@ -184,7 +179,7 @@ export default function SeerProjectTable() { project={project} isFetchingSettings={isFetchingSettings} autofixSettings={{ - ...getDefaultAutofixSettings(organization, project.id), + ...getDefaultAutofixSettings(project.id), ...autofixSettingsByProjectId.get(project.id), ...mutationData[project.id], }} diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx index d9e8ff5c86d859..567e4b87f637c5 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx @@ -33,11 +33,11 @@ const COLUMNS = [ {title: t('PR Creation'), key: 'pr_creation'}, { title: ( - + {t('Background Agent')} diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx index 2572ea2052cef9..180ab3f0d426eb 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx @@ -47,18 +47,17 @@ export default function SeerProjectTableRow({ // We used to support multiple sensitivity values for Auto-Fix. Now we only support 'off' and 'medium'. // If any other value is set, we treat it as 'medium'. - const hasAutoFixEnabled = - autofixSettings.autofixAutomationTuning !== undefined && - autofixSettings.autofixAutomationTuning !== 'off'; + const isAutoFixEnabled = Boolean( + autofixSettings.autofixAutomationTuning && + autofixSettings.autofixAutomationTuning !== 'off' + ); // We used to have multiple stopping points for PR Creation. // `code_changes` means seer will output code changes, and you can copy and paste them into a new branch. // `open_pr` means seer will take those changes and push a PR for you. - // `background_agent` is a special value that indicates that the PR creation is delegated to a background agent. // All other values are treated as `code_changes`. Which means both checkboxes will be unchecked. - const hasCreatePREnabled = autofixSettings.automatedRunStoppingPoint === 'open_pr'; - const hasDelegationEnabled = - autofixSettings.automatedRunStoppingPoint === 'background_agent'; + const isCreatePrEnabled = autofixSettings.automatedRunStoppingPoint !== 'code_changes'; + const isBackgroundAgentEnabled = Boolean(autofixSettings.automationHandoff); return ( @@ -83,12 +82,14 @@ export default function SeerProjectTableRow({ ) : ( { - const autofixAutomationTuning = e.target.checked ? 'medium' : 'off'; addLoadingMessage(t('Updating Auto-Fix for %s', project.name)); updateBulkAutofixAutomationSettings( - {projectIds: [project.id], autofixAutomationTuning}, + { + projectIds: [project.id], + autofixAutomationTuning: e.target.checked ? 'medium' : 'off', + }, { onError: () => addErrorMessage(t('Problem updating Auto-Fix for %s', project.name)), @@ -101,7 +102,7 @@ export default function SeerProjectTableRow({ )} - {hasDelegationEnabled ? ( + {isBackgroundAgentEnabled ? ( {'n/a'} ) : ( { const automatedRunStoppingPoint = e.target.checked ? 'open_pr' @@ -141,28 +142,11 @@ export default function SeerProjectTableRow({ ) : ( { - // This preference can only be turned off, not on, from here. - // You need to go to the project settings page to turn it on. - addLoadingMessage(t('Updating background agent for %s', project.name)); - updateBulkAutofixAutomationSettings( - { - projectIds: [project.id], - automatedRunStoppingPoint: 'background_agent', - }, - { - onError: () => - addErrorMessage( - t('Failed to update background agent for %s', project.name) - ), - onSuccess: () => - addSuccessMessage( - t('Updated background agent for %s', project.name) - ), - } - ); + // This preference has complicated configuration, and thus cannot be + // turned off or on from here. }} />