-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(seer): Enable bulk editing seer automation #91807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
8658373
f7d5090
3c5c44e
3f9d722
fd73191
57e5fb7
fb80109
03ebb4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reloadProjects here won't do anything since we're using useDetailedProject to fetch additional project details. see https://github.com/getsentry/sentry/blob/master/static/app/utils/useDetailedProject.tsx#L18-L27 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha, switching to invalidating the detailed project queries if that works |
||
const [page, setPage] = useState(1); | ||
const [selected, setSelected] = useState<Set<string>>(new Set()); | ||
roaga marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (fetching) { | ||
return <LoadingIndicator />; | ||
|
@@ -76,17 +86,89 @@ 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...', {duration: 30000}); | ||
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}`)), | ||
}); | ||
}); | ||
roaga marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
); | ||
addSuccessMessage('Projects updated successfully'); | ||
} catch (err) { | ||
addErrorMessage('Failed to update some projects'); | ||
} finally { | ||
await reloadProjects(); | ||
} | ||
} | ||
|
||
const actionMenuItems = SEER_THRESHOLD_MAP.map(key => ({ | ||
key, | ||
label: formatSeerValue(key), | ||
onAction: () => updateProjectsSeerValue(key), | ||
})); | ||
|
||
return ( | ||
<Fragment> | ||
<Panel> | ||
<PanelHeader> | ||
<div>{t('Current Project Settings')}</div> | ||
{selected.size > 0 ? ( | ||
<ActionDropdownMenu | ||
items={actionMenuItems} | ||
triggerLabel={t('Set to')} | ||
size="sm" | ||
/> | ||
) : ( | ||
<div>{t('Current Project Settings')}</div> | ||
)} | ||
<div style={{marginLeft: 'auto'}}> | ||
<Button size="sm" onClick={toggleSelectAll}> | ||
{allSelected ? t('Unselect All') : t('Select All')} | ||
</Button> | ||
</div> | ||
</PanelHeader> | ||
<PanelBody> | ||
{paginatedProjects.map(project => ( | ||
<PanelItem key={project.id}> | ||
<Flex justify="space-between" gap={space(2)} flex={1}> | ||
<Flex gap={space(1)} align="center"> | ||
<Checkbox | ||
checked={selected.has(project.id)} | ||
onChange={() => toggleProject(project.id)} | ||
aria-label={t('Select project')} | ||
roaga marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
<ProjectAvatar project={project} title={project.slug} /> | ||
<Link | ||
to={`/settings/${organization.slug}/projects/${project.slug}/seer/`} | ||
|
@@ -127,3 +209,10 @@ export function SeerAutomationProjectList() { | |
const SeerValue = styled('div')` | ||
color: ${p => p.theme.subText}; | ||
`; | ||
|
||
const ActionDropdownMenu = styled(DropdownMenu)` | ||
[data-test-id='menu-list-item-label'] { | ||
font-weight: normal; | ||
text-transform: none; | ||
} | ||
`; |
Uh oh!
There was an error while loading. Please reload this page.