Skip to content

Commit f922376

Browse files
authored
chore(seer): Settings improvements (#94940)
1 parent 9db004b commit f922376

File tree

5 files changed

+101
-24
lines changed

5 files changed

+101
-24
lines changed

static/app/views/issueDetails/streamline/sidebar/seerNotices.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,8 @@ function CustomStepButtons({
6262
export function SeerNotices({groupId, hasGithubIntegration, project}: SeerNoticesProps) {
6363
const organization = useOrganization();
6464
const {repos} = useAutofixRepos(groupId);
65-
const {
66-
preference,
67-
codeMappingRepos,
68-
isLoading: isLoadingPreferences,
69-
} = useProjectSeerPreferences(project);
65+
const {preference, isLoading: isLoadingPreferences} =
66+
useProjectSeerPreferences(project);
7067
const {starredViews: views} = useStarredIssueViews();
7168

7269
const detailedProject = useDetailedProject({
@@ -88,8 +85,7 @@ export function SeerNotices({groupId, hasGithubIntegration, project}: SeerNotice
8885

8986
// Onboarding conditions
9087
const needsGithubIntegration = !hasGithubIntegration;
91-
const needsRepoSelection =
92-
repos.length === 0 && !preference?.repositories?.length && !codeMappingRepos?.length;
88+
const needsRepoSelection = repos.length === 0 && !preference?.repositories?.length;
9389
const needsAutomation =
9490
detailedProject?.data &&
9591
(detailedProject?.data?.autofixAutomationTuning === 'off' ||

static/app/views/settings/projectSeer/index.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import Form from 'sentry/components/forms/form';
1313
import JsonForm from 'sentry/components/forms/jsonForm';
1414
import type {FieldObject, JsonFormObject} from 'sentry/components/forms/types';
1515
import HookOrDefault from 'sentry/components/hookOrDefault';
16+
import {NoAccess} from 'sentry/components/noAccess';
1617
import Placeholder from 'sentry/components/placeholder';
1718
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
1819
import {t, tct} from 'sentry/locale';
1920
import ProjectsStore from 'sentry/stores/projectsStore';
2021
import {space} from 'sentry/styles/space';
2122
import {DataCategoryExact} from 'sentry/types/core';
2223
import type {Project} from 'sentry/types/project';
24+
import {singleLineRenderer} from 'sentry/utils/marked/marked';
2325
import type {ApiQueryKey} from 'sentry/utils/queryClient';
2426
import {setApiQueryData} from 'sentry/utils/queryClient';
2527
import useOrganization from 'sentry/utils/useOrganization';
@@ -247,6 +249,10 @@ function ProjectSeer({project}: ProjectSeerProps) {
247249
!setupAcknowledgement.orgHasAcknowledged ||
248250
(!billing.hasAutofixQuota && organization.features.includes('seer-billing'));
249251

252+
if (organization.hideAiFeatures) {
253+
return <NoAccess />;
254+
}
255+
250256
if (isLoading) {
251257
return (
252258
<Fragment>
@@ -281,7 +287,17 @@ function ProjectSeer({project}: ProjectSeerProps) {
281287
title={t('Project Seer Settings')}
282288
projectSlug={project.slug}
283289
/>
284-
<SettingsPageHeader title={t('Seer')} />
290+
<SettingsPageHeader
291+
title={tct('Seer Settings for [projectName]', {
292+
projectName: (
293+
<span
294+
dangerouslySetInnerHTML={{
295+
__html: singleLineRenderer(`\`${project.slug}\``),
296+
}}
297+
/>
298+
),
299+
})}
300+
/>
285301
{organization.features.includes('trigger-autofix-on-issue-summary') && (
286302
<ProjectSeerGeneralForm project={project} />
287303
)}

static/gsApp/views/seerAutomation/index.spec.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import SeerAutomationRoot from './index';
99

1010
describe('SeerAutomation', function () {
1111
beforeEach(() => {
12-
// Mock the seer setup check endpoint for all tests
1312
MockApiClient.addMockResponse({
1413
url: '/organizations/org-slug/seer/setup-check/',
1514
method: 'GET',
@@ -24,6 +23,13 @@ describe('SeerAutomation', function () {
2423
},
2524
},
2625
});
26+
MockApiClient.addMockResponse({
27+
url: '/projects/org-slug/project-slug/seer/preferences/',
28+
method: 'GET',
29+
body: {
30+
repositories: [],
31+
},
32+
});
2733
});
2834

2935
afterEach(() => {

static/gsApp/views/seerAutomation/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ function SeerAutomationRoot() {
2525
const organization = useOrganization();
2626
const {isLoading, billing, setupAcknowledgement} = useOrganizationSeerSetup();
2727

28-
if (!organization.features.includes('trigger-autofix-on-issue-summary')) {
28+
if (
29+
!organization.features.includes('trigger-autofix-on-issue-summary') ||
30+
organization.hideAiFeatures
31+
) {
2932
return <NoAccess />;
3033
}
3134

static/gsApp/views/seerAutomation/seerAutomationProjectList.tsx

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Fragment, useState} from 'react';
1+
import {Fragment, useMemo, useState} from 'react';
22
import styled from '@emotion/styled';
33

44
import {
@@ -15,13 +15,15 @@ import {Flex} from 'sentry/components/core/layout';
1515
import {Link} from 'sentry/components/core/link';
1616
import {Tooltip} from 'sentry/components/core/tooltip';
1717
import {DropdownMenu} from 'sentry/components/dropdownMenu';
18+
import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
1819
import LoadingError from 'sentry/components/loadingError';
1920
import LoadingIndicator from 'sentry/components/loadingIndicator';
2021
import Panel from 'sentry/components/panels/panel';
2122
import PanelBody from 'sentry/components/panels/panelBody';
2223
import PanelHeader from 'sentry/components/panels/panelHeader';
2324
import PanelItem from 'sentry/components/panels/panelItem';
2425
import Placeholder from 'sentry/components/placeholder';
26+
import SearchBar from 'sentry/components/searchBar';
2527
import {IconChevron} from 'sentry/icons';
2628
import {t} from 'sentry/locale';
2729
import {space} from 'sentry/styles/space';
@@ -44,7 +46,10 @@ function ProjectSeerSetting({project, orgSlug}: {orgSlug: string; project: Proje
4446
projectSlug: project.slug,
4547
});
4648

47-
if (detailedProject.isPending) {
49+
const {preference, isPending: isLoadingPreferences} =
50+
useProjectSeerPreferences(project);
51+
52+
if (detailedProject.isPending || isLoadingPreferences) {
4853
return (
4954
<div>
5055
<Placeholder height="12px" width="50px" />
@@ -59,17 +64,21 @@ function ProjectSeerSetting({project, orgSlug}: {orgSlug: string; project: Proje
5964
const {autofixAutomationTuning = 'off', seerScannerAutomation = false} =
6065
detailedProject.data;
6166

67+
const repoCount = preference?.repositories?.length || 0;
68+
6269
return (
6370
<SeerValue>
6471
<span>
6572
<Subheading>{t('Scans')}:</Subheading>{' '}
6673
{seerScannerAutomation ? t('On') : t('Off')}
6774
</span>
68-
{' | '}
6975
<span>
7076
<Subheading>{t('Fixes')}:</Subheading>{' '}
7177
{getSeerLabel(autofixAutomationTuning, seerScannerAutomation)}
7278
</span>
79+
<span>
80+
<Subheading>{t('Repos')}:</Subheading> {repoCount}
81+
</span>
7382
</SeerValue>
7483
);
7584
}
@@ -115,6 +124,18 @@ export function SeerAutomationProjectList() {
115124
const [page, setPage] = useState(1);
116125
const [selected, setSelected] = useState<Set<string>>(() => new Set());
117126
const queryClient = useQueryClient();
127+
const [search, setSearch] = useState('');
128+
129+
const filteredProjects = useMemo(() => {
130+
return projects.filter(project =>
131+
project.slug.toLowerCase().includes(search.toLowerCase())
132+
);
133+
}, [projects, search]);
134+
135+
const handleSearchChange = (searchQuery: string) => {
136+
setSearch(searchQuery);
137+
setPage(1); // Reset to first page when search changes
138+
};
118139

119140
if (fetching) {
120141
return <LoadingIndicator />;
@@ -124,10 +145,10 @@ export function SeerAutomationProjectList() {
124145
return <LoadingError />;
125146
}
126147

127-
const totalProjects = projects.length;
148+
const totalProjects = filteredProjects.length;
128149
const pageStart = (page - 1) * PROJECTS_PER_PAGE;
129150
const pageEnd = page * PROJECTS_PER_PAGE;
130-
const paginatedProjects = projects.slice(pageStart, pageEnd);
151+
const paginatedProjects = filteredProjects.slice(pageStart, pageEnd);
131152

132153
const previousDisabled = page <= 1;
133154
const nextDisabled = pageEnd >= totalProjects;
@@ -140,14 +161,24 @@ export function SeerAutomationProjectList() {
140161
setPage(p => p + 1);
141162
};
142163

143-
const allSelected = selected.size === projects.length && projects.length > 0;
164+
const allFilteredSelected =
165+
filteredProjects.length > 0 &&
166+
filteredProjects.every(project => selected.has(project.id));
144167
const toggleSelectAll = () => {
145-
if (allSelected) {
146-
// Unselect all projects
147-
setSelected(new Set());
168+
if (allFilteredSelected) {
169+
// Unselect all filtered projects
170+
setSelected(prev => {
171+
const newSet = new Set(prev);
172+
filteredProjects.forEach(project => newSet.delete(project.id));
173+
return newSet;
174+
});
148175
} else {
149-
// Select all projects
150-
setSelected(new Set(projects.map(project => project.id)));
176+
// Select all filtered projects
177+
setSelected(prev => {
178+
const newSet = new Set(prev);
179+
filteredProjects.forEach(project => newSet.add(project.id));
180+
return newSet;
181+
});
151182
}
152183
};
153184

@@ -244,6 +275,13 @@ export function SeerAutomationProjectList() {
244275

245276
return (
246277
<Fragment>
278+
<SearchWrapper>
279+
<SearchBar
280+
query={search}
281+
onChange={handleSearchChange}
282+
placeholder={t('Search projects')}
283+
/>
284+
</SearchWrapper>
247285
<Panel>
248286
<PanelHeader hasButtons>
249287
{selected.size > 0 ? (
@@ -264,20 +302,25 @@ export function SeerAutomationProjectList() {
264302
)}
265303
<div style={{marginLeft: 'auto'}}>
266304
<Button size="sm" onClick={toggleSelectAll}>
267-
{allSelected ? t('Unselect All') : t('Select All')}
305+
{allFilteredSelected ? t('Unselect All') : t('Select All')}
268306
</Button>
269307
</div>
270308
</PanelHeader>
271309
<PanelBody>
310+
{filteredProjects.length === 0 && search && (
311+
<div style={{padding: space(2), textAlign: 'center', color: '#888'}}>
312+
{t('No projects found matching "%(search)s"', {search})}
313+
</div>
314+
)}
272315
{paginatedProjects.map(project => (
273316
<PanelItem key={project.id}>
274-
<Flex justify="space-between" gap={space(2)} flex={1}>
317+
<Flex justify="space-between" align="center" gap={space(2)} flex={1}>
275318
<Flex gap={space(1)} align="center">
276319
<Tooltip
277320
title={t('You do not have permission to edit this project')}
278321
disabled={projectsWithWriteAccess.includes(project)}
279322
>
280-
<Checkbox
323+
<StyledCheckbox
281324
checked={selected.has(project.id)}
282325
onChange={() => toggleProject(project.id)}
283326
aria-label={t('Toggle project')}
@@ -321,8 +364,15 @@ export function SeerAutomationProjectList() {
321364
);
322365
}
323366

367+
const SearchWrapper = styled('div')`
368+
margin-bottom: ${space(2)};
369+
`;
370+
324371
const SeerValue = styled('div')`
325372
color: ${p => p.theme.subText};
373+
display: flex;
374+
justify-content: flex-end;
375+
gap: ${space(4)};
326376
`;
327377

328378
const ActionDropdownMenu = styled(DropdownMenu)`
@@ -331,3 +381,9 @@ const ActionDropdownMenu = styled(DropdownMenu)`
331381
text-transform: none;
332382
}
333383
`;
384+
385+
const StyledCheckbox = styled(Checkbox)`
386+
margin-bottom: 0;
387+
padding-bottom: 0;
388+
padding-top: ${space(0.5)};
389+
`;

0 commit comments

Comments
 (0)