From 8658373fc095c211e12ac3d34bd68aa0d2838793 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 16 May 2025 10:09:19 -0700 Subject: [PATCH 1/8] Enable bulk editing seer automation --- static/gsApp/views/seerAutomation/index.tsx | 7 +- .../seerAutomationProjectList.tsx | 94 ++++++++++++++++++- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/static/gsApp/views/seerAutomation/index.tsx b/static/gsApp/views/seerAutomation/index.tsx index bc269670bbc25f..23cb9e721dd42d 100644 --- a/static/gsApp/views/seerAutomation/index.tsx +++ b/static/gsApp/views/seerAutomation/index.tsx @@ -20,7 +20,12 @@ function SeerAutomationRoot() { return ( - + diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index 309c59a5f09eec..4bf2eb10d36d30 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -1,10 +1,17 @@ import {Fragment, useState} from 'react'; import styled from '@emotion/styled'; +import { + addErrorMessage, + addLoadingMessage, + addSuccessMessage, +} from 'sentry/actionCreators/indicator'; import {Flex} from 'sentry/components/container/flex'; import {ProjectAvatar} from 'sentry/components/core/avatar/projectAvatar'; import {Button} from 'sentry/components/core/button'; import {ButtonBar} from 'sentry/components/core/button/buttonBar'; +import {Checkbox} from 'sentry/components/core/checkbox'; +import {DropdownMenu} from 'sentry/components/dropdownMenu'; import Link from 'sentry/components/links/link'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; @@ -17,10 +24,11 @@ import {IconChevron} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Project} from 'sentry/types/project'; +import useApi from 'sentry/utils/useApi'; import {useDetailedProject} from 'sentry/utils/useDetailedProject'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; -import {formatSeerValue} from 'sentry/views/settings/projectSeer'; +import {formatSeerValue, SEER_THRESHOLD_MAP} from 'sentry/views/settings/projectSeer'; const PROJECTS_PER_PAGE = 20; @@ -49,8 +57,10 @@ function ProjectSeerSetting({project, orgSlug}: {orgSlug: string; project: Proje export function SeerAutomationProjectList() { const organization = useOrganization(); - const {projects, fetching, fetchError} = useProjects(); + const api = useApi(); + const {projects, fetching, fetchError, reloadProjects} = useProjects(); const [page, setPage] = useState(1); + const [selected, setSelected] = useState>(new Set()); if (fetching) { return ; @@ -76,17 +86,88 @@ export function SeerAutomationProjectList() { setPage(p => p + 1); }; + const allSelected = selected.size === projects.length && projects.length > 0; + const toggleSelectAll = () => { + if (allSelected) { + // Unselect all projects + setSelected(new Set()); + } else { + // Select all projects + setSelected(new Set(projects.map(project => project.id))); + } + }; + + const toggleProject = (projectId: string) => { + setSelected(prev => { + const newSet = new Set(prev); + if (newSet.has(projectId)) { + newSet.delete(projectId); + } else { + newSet.add(projectId); + } + return newSet; + }); + }; + + async function updateProjectsSeerValue(value: string) { + addLoadingMessage('Updating projects...'); + try { + await Promise.all( + Array.from(selected).map(projectId => { + const project = projects.find(p => p.id === projectId); + if (!project) return Promise.resolve(); + return new Promise((resolve, reject) => { + api.request(`/projects/${organization.slug}/${project.slug}/`, { + method: 'PUT', + data: {autofixAutomationTuning: value}, + success: () => resolve(undefined), + error: () => reject(new Error(`Failed to update ${project.slug}`)), + }); + }); + }) + ); + addSuccessMessage('Projects updated successfully'); + await reloadProjects(); + } catch (err) { + addErrorMessage('Failed to update some projects'); + } + } + + const actionMenuItems = SEER_THRESHOLD_MAP.map(key => ({ + key, + label: formatSeerValue(key), + onAction: () => updateProjectsSeerValue(key), + })); + return ( -
{t('Current Project Settings')}
+ {selected.size > 0 ? ( + + ) : ( +
{t('Current Project Settings')}
+ )} +
+ +
{paginatedProjects.map(project => ( + toggleProject(project.id)} + aria-label={t('Select project')} + /> p.theme.subText}; `; + +const ActionDropdownMenu = styled(DropdownMenu)` + [data-test-id='menu-list-item-label'] { + font-weight: normal; + text-transform: none; + } +`; From f7d50906f78c5c97030c46363f1a497a809875bf Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 16 May 2025 11:57:04 -0700 Subject: [PATCH 2/8] Fix --- .../gsApp/views/seerAutomation/seerAutomationProjectList.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index 4bf2eb10d36d30..179689faf871cd 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -110,7 +110,7 @@ export function SeerAutomationProjectList() { }; async function updateProjectsSeerValue(value: string) { - addLoadingMessage('Updating projects...'); + addLoadingMessage('Updating projects...', {duration: 30000}); try { await Promise.all( Array.from(selected).map(projectId => { @@ -127,9 +127,10 @@ export function SeerAutomationProjectList() { }) ); addSuccessMessage('Projects updated successfully'); - await reloadProjects(); } catch (err) { addErrorMessage('Failed to update some projects'); + } finally { + await reloadProjects(); } } From 3c5c44e22652e0df8dfeb33553ad60063add6b67 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal <47861399+roaga@users.noreply.github.com> Date: Fri, 16 May 2025 13:34:21 -0700 Subject: [PATCH 3/8] Update static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx Co-authored-by: Scott Cooper --- static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index 179689faf871cd..c57ddd8f49ee73 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -60,7 +60,7 @@ export function SeerAutomationProjectList() { const api = useApi(); const {projects, fetching, fetchError, reloadProjects} = useProjects(); const [page, setPage] = useState(1); - const [selected, setSelected] = useState>(new Set()); + const [selected, setSelected] = useState>(() => new Set()); if (fetching) { return ; From 3f9d7227cd622722f385acc1e23fae979f62873c Mon Sep 17 00:00:00 2001 From: Rohan Agarwal <47861399+roaga@users.noreply.github.com> Date: Fri, 16 May 2025 13:38:48 -0700 Subject: [PATCH 4/8] Update static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx Co-authored-by: Scott Cooper --- static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index c57ddd8f49ee73..4bd9756820e288 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -167,7 +167,7 @@ export function SeerAutomationProjectList() { toggleProject(project.id)} - aria-label={t('Select project')} + aria-label={t('Toggle project')} /> Date: Fri, 16 May 2025 13:39:33 -0700 Subject: [PATCH 5/8] Update static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx Co-authored-by: Scott Cooper --- .../views/seerAutomation/seerAutomationProjectList.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index 4bd9756820e288..ecb518563e4757 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -116,14 +116,10 @@ export function SeerAutomationProjectList() { Array.from(selected).map(projectId => { const project = projects.find(p => p.id === projectId); if (!project) return Promise.resolve(); - return new Promise((resolve, reject) => { - api.request(`/projects/${organization.slug}/${project.slug}/`, { + return api.requestPromise(`/projects/${organization.slug}/${project.slug}/`, { method: 'PUT', data: {autofixAutomationTuning: value}, - success: () => resolve(undefined), - error: () => reject(new Error(`Failed to update ${project.slug}`)), }); - }); }) ); addSuccessMessage('Projects updated successfully'); From 57e5fb791eef191d28368cc0687d2a2ccd39a6e3 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 20:40:20 +0000 Subject: [PATCH 6/8] :hammer_and_wrench: apply pre-commit fixes --- .../views/seerAutomation/seerAutomationProjectList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index ecb518563e4757..5934404923888e 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -117,9 +117,9 @@ export function SeerAutomationProjectList() { const project = projects.find(p => p.id === projectId); if (!project) return Promise.resolve(); return api.requestPromise(`/projects/${organization.slug}/${project.slug}/`, { - method: 'PUT', - data: {autofixAutomationTuning: value}, - }); + method: 'PUT', + data: {autofixAutomationTuning: value}, + }); }) ); addSuccessMessage('Projects updated successfully'); From fb80109e46a7853f3a3f7e66f14b09e9af4456b0 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal Date: Fri, 16 May 2025 13:48:00 -0700 Subject: [PATCH 7/8] Invalidate detailed queries --- .../seerAutomationProjectList.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index 5934404923888e..39df1a25fcf029 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -24,8 +24,12 @@ import {IconChevron} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Project} from 'sentry/types/project'; +import {useQueryClient} from 'sentry/utils/queryClient'; import useApi from 'sentry/utils/useApi'; -import {useDetailedProject} from 'sentry/utils/useDetailedProject'; +import { + makeDetailedProjectQueryKey, + useDetailedProject, +} from 'sentry/utils/useDetailedProject'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import {formatSeerValue, SEER_THRESHOLD_MAP} from 'sentry/views/settings/projectSeer'; @@ -58,9 +62,10 @@ function ProjectSeerSetting({project, orgSlug}: {orgSlug: string; project: Proje export function SeerAutomationProjectList() { const organization = useOrganization(); const api = useApi(); - const {projects, fetching, fetchError, reloadProjects} = useProjects(); + const {projects, fetching, fetchError} = useProjects(); const [page, setPage] = useState(1); const [selected, setSelected] = useState>(() => new Set()); + const queryClient = useQueryClient(); if (fetching) { return ; @@ -126,7 +131,16 @@ export function SeerAutomationProjectList() { } catch (err) { addErrorMessage('Failed to update some projects'); } finally { - await reloadProjects(); + Array.from(selected).forEach(projectId => { + const project = projects.find(p => p.id === projectId); + if (!project) return; + queryClient.invalidateQueries({ + queryKey: makeDetailedProjectQueryKey({ + orgSlug: organization.slug, + projectSlug: project.slug, + }), + }); + }); } } From 03ebb4b94a37e3aaef7ca557fa58b90677149b16 Mon Sep 17 00:00:00 2001 From: Rohan Agarwal <47861399+roaga@users.noreply.github.com> Date: Fri, 16 May 2025 13:50:26 -0700 Subject: [PATCH 8/8] Update static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx Co-authored-by: Scott Cooper --- static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx index 39df1a25fcf029..c0e9c05a99e4b8 100644 --- a/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx +++ b/static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx @@ -61,7 +61,7 @@ function ProjectSeerSetting({project, orgSlug}: {orgSlug: string; project: Proje export function SeerAutomationProjectList() { const organization = useOrganization(); - const api = useApi(); + const api = useApi({persistInFlight: true}); const {projects, fetching, fetchError} = useProjects(); const [page, setPage] = useState(1); const [selected, setSelected] = useState>(() => new Set());