From 7a89eef879a3a759a7918e4e7d402264149a8516 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Fri, 3 Apr 2026 15:53:13 -0700 Subject: [PATCH 1/2] feat(seer): Setup Agent bulk-edit on the Seer Autofix list page, and bring back Create PR bulk edits --- .../seer/overview/autofixOverviewSection.tsx | 9 ++++--- .../overview/utils/seerPreferredAgent.spec.ts | 18 +++++-------- .../seer/overview/utils/seerPreferredAgent.ts | 6 ++--- .../projectTable/seerProjectTable.tsx | 2 +- .../projectTable/seerProjectTableHeader.tsx | 27 ++++++++++++++++--- .../projectTable/seerProjectTableRow.tsx | 26 ++++++++++-------- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/static/app/views/settings/seer/overview/autofixOverviewSection.tsx b/static/app/views/settings/seer/overview/autofixOverviewSection.tsx index e198fdd13938b1..a3599266ca6b28 100644 --- a/static/app/views/settings/seer/overview/autofixOverviewSection.tsx +++ b/static/app/views/settings/seer/overview/autofixOverviewSection.tsx @@ -161,9 +161,7 @@ function AgentNameForm({ const preferredAgent = useFetchPreferredAgent({organization}); const codingAgentSelectOptions = useFetchAgentOptions({organization}); const codingAgentMutationOptions = getPreferredAgentMutationOptions({organization}); - const bulkMutateSelectedAgent = useBulkMutateSelectedAgent({ - projects: projects.filter(p => !projectsIdsWithPreferredAgent.has(p.id)), - }); + const bulkMutateSelectedAgent = useBulkMutateSelectedAgent(); const preferredAgentLabel = codingAgentSelectOptions.data?.find( o => o.value === preferredAgent.data @@ -225,7 +223,10 @@ function AgentNameForm({ onClick={async () => { if (preferredAgent.data) { setIsBulkMutatingAgent(true); - await bulkMutateSelectedAgent(preferredAgent.data); + await bulkMutateSelectedAgent( + projects.filter(p => !projectsIdsWithPreferredAgent.has(p.id)), + preferredAgent.data + ); setIsBulkMutatingAgent(false); } else { addErrorMessage(t('No coding agent integration found')); diff --git a/static/app/views/settings/seer/overview/utils/seerPreferredAgent.spec.ts b/static/app/views/settings/seer/overview/utils/seerPreferredAgent.spec.ts index 3097deb833d92b..c89d2958b08e19 100644 --- a/static/app/views/settings/seer/overview/utils/seerPreferredAgent.spec.ts +++ b/static/app/views/settings/seer/overview/utils/seerPreferredAgent.spec.ts @@ -295,11 +295,10 @@ describe('seerPreferredAgent', () => { const {result} = renderHookWithProviders(useBulkMutateSelectedAgent, { organization, - initialProps: {projects: [project]}, }); await act(async () => { - await result.current('seer'); + await result.current([project], 'seer'); }); expect(projectPutRequest).toHaveBeenCalledWith( @@ -328,11 +327,10 @@ describe('seerPreferredAgent', () => { const {result} = renderHookWithProviders(useBulkMutateSelectedAgent, { organization, - initialProps: {projects: [project]}, }); await act(async () => { - await result.current(integration); + await result.current([project], integration); }); expect(projectPutRequest).toHaveBeenCalledWith( @@ -368,10 +366,9 @@ describe('seerPreferredAgent', () => { const {result} = renderHookWithProviders(useBulkMutateSelectedAgent, { organization, - initialProps: {projects: [project]}, }); await act(async () => { - await result.current(integration); + await result.current([project], integration); }); expect(seerPreferencesPostRequest).toHaveBeenCalledWith( @@ -392,11 +389,10 @@ describe('seerPreferredAgent', () => { const {result} = renderHookWithProviders(useBulkMutateSelectedAgent, { organization, - initialProps: {projects: [project]}, }); await act(async () => { - await result.current('seer'); + await result.current([project], 'seer'); }); expect(updateSuccessSpy).toHaveBeenCalledWith({ @@ -427,11 +423,10 @@ describe('seerPreferredAgent', () => { const {result} = renderHookWithProviders(useBulkMutateSelectedAgent, { organization, - initialProps: {projects: [project]}, }); await act(async () => { - await result.current('seer'); + await result.current([project], 'seer'); }); expect(addErrorMessageSpy).toHaveBeenCalledWith( @@ -461,11 +456,10 @@ describe('seerPreferredAgent', () => { const {result} = renderHookWithProviders(useBulkMutateSelectedAgent, { organization, - initialProps: {projects: [project]}, }); await act(async () => { - await result.current('seer'); + await result.current([project], 'seer'); }); expect(addErrorMessageSpy).toHaveBeenCalledWith( diff --git a/static/app/views/settings/seer/overview/utils/seerPreferredAgent.ts b/static/app/views/settings/seer/overview/utils/seerPreferredAgent.ts index ce7bd5f611e09c..28b05d7b9ec485 100644 --- a/static/app/views/settings/seer/overview/utils/seerPreferredAgent.ts +++ b/static/app/views/settings/seer/overview/utils/seerPreferredAgent.ts @@ -101,7 +101,7 @@ export function getPreferredAgentMutationOptions({ }); } -export function useBulkMutateSelectedAgent({projects}: {projects: Project[]}) { +export function useBulkMutateSelectedAgent() { const organization = useOrganization(); const queryClient = useQueryClient(); const autofixSettingsQueryOptions = bulkAutofixAutomationSettingsInfiniteOptions({ @@ -109,7 +109,7 @@ export function useBulkMutateSelectedAgent({projects}: {projects: Project[]}) { }); return useCallback( - async (integration: PreferredAgent) => { + async (projects: Project[], integration: PreferredAgent) => { const results = await processInChunks({ items: projects, chunkSize: 15, @@ -184,6 +184,6 @@ export function useBulkMutateSelectedAgent({projects}: {projects: Project[]}) { } } }, - [projects, organization, queryClient, autofixSettingsQueryOptions.queryKey] + [organization, queryClient, autofixSettingsQueryOptions.queryKey] ); } diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx index 3b40eb076883e7..d89259ee7cf182 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx @@ -280,6 +280,6 @@ const FiltersContainer = styled('div')` `; const SimpleTableWithColumns = styled(SimpleTable)` - grid-template-columns: 3fr minmax(300px, 1fr) repeat(2, max-content); + grid-template-columns: max-content 3fr minmax(300px, 1fr) repeat(2, max-content); overflow: visible; `; diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx index ec4deaf2c2f872..5de5c5248e26b1 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx @@ -16,6 +16,10 @@ import {parseQueryKey} from 'sentry/utils/api/apiQueryKey'; import type {Sort} from 'sentry/utils/discover/fields'; import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState'; import {useOrganization} from 'sentry/utils/useOrganization'; +import { + useBulkMutateSelectedAgent, + useFetchAgentOptions, +} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent'; import {useCanWriteSettings} from 'getsentry/views/seerAutomation/components/useCanWriteSettings'; @@ -91,15 +95,18 @@ export function ProjectTableHeader({ [projects, selectedIds] ); + const agentOptions = useFetchAgentOptions({organization}); + const bulkMutateSelectedAgent = useBulkMutateSelectedAgent(); + return ( - {/* + - */} + {COLUMNS.map(({title, key, sortKey}) => ( + ({ + key: typeof value === 'object' ? value.provider : value, + label, + onAction: () => { + bulkMutateSelectedAgent(projects, value); + }, + })) ?? [] + } + triggerLabel={t('Agent')} + /> diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx index a2f6aaccb71dac..73c0b6404fd40c 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx @@ -1,3 +1,6 @@ +import styled from '@emotion/styled'; + +import {Checkbox} from '@sentry/scraps/checkbox'; import {InfoTip} from '@sentry/scraps/info'; import {Flex, Stack} from '@sentry/scraps/layout'; import {Link} from '@sentry/scraps/link'; @@ -15,6 +18,7 @@ import {SimpleTable} from 'sentry/components/tables/simpleTable'; import {IconWarning} from 'sentry/icons/iconWarning'; import {t, tct} from 'sentry/locale'; import type {Project} from 'sentry/types/project'; +import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState'; import {useOrganization} from 'sentry/utils/useOrganization'; import { useAgentOptions, @@ -40,7 +44,7 @@ export function SeerProjectTableRow({ }: Props) { const organization = useOrganization(); const canWrite = useCanWriteSettings(); - // const {isSelected, toggleSelected} = useListItemCheckboxContext(); + const {isSelected, toggleSelected} = useListItemCheckboxContext(); const options = useAgentOptions({integrations: integrations ?? []}); const autofixAgent = useSelectedAgentFromBulkSettings({ @@ -66,7 +70,7 @@ export function SeerProjectTableRow({ return ( - {/* + toggleSelected(project.id)} /> - */} + @@ -194,11 +198,11 @@ export function SeerProjectTableRow({ ); } -// const CheckboxClickTarget = styled('label')` -// cursor: pointer; -// display: block; -// margin: -${p => p.theme.space.md}; -// padding: ${p => p.theme.space.md}; -// max-width: unset; -// line-height: 0; -// `; +const CheckboxClickTarget = styled('label')` + cursor: pointer; + display: block; + margin: -${p => p.theme.space.md}; + padding: ${p => p.theme.space.md}; + max-width: unset; + line-height: 0; +`; From d9080b0b2ced73674c306df7cd3c40cdc9c8c6e4 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Mon, 6 Apr 2026 21:25:05 -0700 Subject: [PATCH 2/2] fix(seer): Apply Agent bulk edit only to selected projects The Agent dropdown in the bulk-edit header was passing the full projects prop to bulkMutateSelectedAgent, causing the agent change to apply to all projects in the table view rather than only the user's checkbox selections. Filter projects by projectIds (which already resolves selectedIds === 'all' to all project IDs, or uses the specific selected IDs) to match the behavior of the Create PRs bulk-edit dropdown. Co-Authored-By: Claude Sonnet 4 --- .../components/projectTable/seerProjectTableHeader.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx index 5de5c5248e26b1..b6db2925f8f820 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx @@ -148,7 +148,10 @@ export function ProjectTableHeader({ key: typeof value === 'object' ? value.provider : value, label, onAction: () => { - bulkMutateSelectedAgent(projects, value); + const selectedProjects = projects.filter(p => + projectIds.includes(p.id) + ); + bulkMutateSelectedAgent(selectedProjects, value); }, })) ?? [] }