Skip to content

Commit a545179

Browse files
authored
feat(seer): Rewrite the Seer > Project list page (#109531)
This rewrite consolidates some of the previous columns to match what's in #109349 - We've combined the previous "auto-triggered fixes" and "coding agent" columns into one column with a dropdown to pick an "Autofix Agent". - PR create column now applies to background agents as well as Seer Agent Bulk editing is disabled. At this moment we don't have support for updating the complex `automation_handoff` values in the current bulk edit endpoint. Fixes https://linear.app/getsentry/issue/CW-930/settings-seer-project-list-simplify-the-columnsactions-to-match Depends on #110188
1 parent 32e917b commit a545179

File tree

8 files changed

+1035
-341
lines changed

8 files changed

+1035
-341
lines changed

static/app/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings.ts

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {useCallback, useMemo} from 'react';
1+
import {useMemo} from 'react';
22

33
import type {ProjectSeerPreferences} from 'sentry/components/events/autofix/types';
4+
import type {Organization} from 'sentry/types/organization';
5+
import {apiOptions} from 'sentry/utils/api/apiOptions';
46
import getApiUrl from 'sentry/utils/api/getApiUrl';
5-
import useFetchSequentialPages from 'sentry/utils/api/useFetchSequentialPages';
67
import {
78
fetchMutation,
89
useMutation,
@@ -48,31 +49,19 @@ export type AutofixAutomationSettings = {
4849
reposCount: number;
4950
};
5051

51-
/**
52-
* Fetch all autofix related settings for all projects.
53-
*
54-
* This returns a list of objects with the following properties:
55-
* - projectId: the project ID
56-
* - autofixAutomationTuning: the tuning setting for automated autofix
57-
* - automatedRunStoppingPoint: the stopping point for automated runs
58-
* - reposCount: the number of repositories configured for the project
59-
*/
60-
export function useGetBulkAutofixAutomationSettings() {
61-
const organization = useOrganization();
62-
63-
return useFetchSequentialPages<AutofixAutomationSettings[]>({
64-
enabled: true,
65-
perPage: 100,
66-
getQueryKey: useCallback(
67-
({cursor, per_page}: {cursor: string; per_page: number}) => [
68-
getApiUrl(`/organizations/$organizationIdOrSlug/autofix/automation-settings/`, {
69-
path: {organizationIdOrSlug: organization.slug},
70-
}),
71-
{query: {cursor, per_page}},
72-
],
73-
[organization.slug]
74-
),
75-
});
52+
export function bulkAutofixAutomationSettingsInfiniteOptions({
53+
organization,
54+
}: {
55+
organization: Organization;
56+
}) {
57+
return apiOptions.asInfinite<AutofixAutomationSettings[]>()(
58+
'/organizations/$organizationIdOrSlug/autofix/automation-settings/',
59+
{
60+
path: {organizationIdOrSlug: organization.slug},
61+
query: {per_page: 100},
62+
staleTime: 0,
63+
}
64+
);
7665
}
7766

7867
type AutofixAutomationUpdate =

static/app/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import {useCallback} from 'react';
2+
13
import {
24
makeProjectSeerPreferencesQueryKey,
35
type SeerPreferencesResponse,
46
} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
57
import type {ProjectSeerPreferences} from 'sentry/components/events/autofix/types';
68
import type {Project} from 'sentry/types/project';
9+
import apiFetch from 'sentry/utils/api/apiFetch';
710
import {
811
fetchMutation,
912
getApiQueryData,
@@ -23,6 +26,21 @@ type Context =
2326
previousPrefs?: never;
2427
};
2528

29+
export function useFetchProjectSeerPreferences({project}: {project: Project}) {
30+
const organization = useOrganization();
31+
const queryClient = useQueryClient();
32+
const queryKey = makeProjectSeerPreferencesQueryKey(organization.slug, project.slug);
33+
34+
return useCallback(async () => {
35+
const response = await queryClient.fetchQuery({
36+
queryKey,
37+
queryFn: apiFetch<SeerPreferencesResponse>,
38+
staleTime: 0,
39+
});
40+
return response.json.preference;
41+
}, [queryClient, queryKey]);
42+
}
43+
2644
export function useUpdateProjectSeerPreferences(project: Project) {
2745
const organization = useOrganization();
2846
const queryClient = useQueryClient();

static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default function AutofixAgent({canWrite, preference, project}: Props) {
6969
project,
7070
integrations: integrations ?? [],
7171
});
72-
const mutateSelectedAgent = useMutateSelectedAgent({preference, project});
72+
const mutateSelectedAgent = useMutateSelectedAgent({project});
7373

7474
const disabledReason = canWrite
7575
? null

static/gsApp/views/seerAutomation/components/projectTable/seerProjectTable.tsx

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,113 @@
1-
import {useMemo, useState} from 'react';
1+
import {useEffect, useMemo} from 'react';
22
import styled from '@emotion/styled';
33
import {debounce, parseAsString, useQueryState} from 'nuqs';
44

55
import {InputGroup} from '@sentry/scraps/input';
66
import {Stack} from '@sentry/scraps/layout';
77

88
import {
9-
useGetBulkAutofixAutomationSettings,
9+
bulkAutofixAutomationSettingsInfiniteOptions,
1010
useUpdateBulkAutofixAutomationSettings,
11-
type AutofixAutomationSettings,
1211
} from 'sentry/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings';
12+
import {organizationIntegrationsCodingAgents} from 'sentry/components/events/autofix/useAutofix';
1313
import LoadingError from 'sentry/components/loadingError';
1414
import LoadingIndicator from 'sentry/components/loadingIndicator';
1515
import {SimpleTable} from 'sentry/components/tables/simpleTable';
1616
import {IconSearch} from 'sentry/icons/iconSearch';
1717
import {t, tct} from 'sentry/locale';
18+
import ProjectsStore from 'sentry/stores/projectsStore';
1819
import type {Project} from 'sentry/types/project';
1920
import type {Sort} from 'sentry/utils/discover/fields';
2021
import {ListItemCheckboxProvider} from 'sentry/utils/list/useListItemCheckboxState';
22+
import {useInfiniteQuery, useQuery, useQueryClient} from 'sentry/utils/queryClient';
2123
import type {ApiQueryKey} from 'sentry/utils/queryClient';
2224
import parseAsSort from 'sentry/utils/url/parseAsSort';
25+
import useOrganization from 'sentry/utils/useOrganization';
2326
import useProjects from 'sentry/utils/useProjects';
2427

2528
import ProjectTableHeader from 'getsentry/views/seerAutomation/components/projectTable/seerProjectTableHeader';
2629
import SeerProjectTableRow from 'getsentry/views/seerAutomation/components/projectTable/seerProjectTableRow';
2730

28-
function getDefaultAutofixSettings(projectId: string): AutofixAutomationSettings {
29-
return {
30-
autofixAutomationTuning: 'off',
31-
automatedRunStoppingPoint: 'code_changes',
32-
automationHandoff: undefined,
33-
projectId,
34-
reposCount: 0,
35-
};
36-
}
37-
3831
export default function SeerProjectTable() {
32+
const queryClient = useQueryClient();
33+
const organization = useOrganization();
3934
const {projects, fetching, fetchError} = useProjects();
4035

41-
const {pages: autofixAutomationSettings, isFetching: isFetchingSettings} =
42-
useGetBulkAutofixAutomationSettings();
36+
const autofixSettingsQueryOptions = bulkAutofixAutomationSettingsInfiniteOptions({
37+
organization,
38+
});
39+
const {
40+
data: autofixAutomationSettings,
41+
hasNextPage,
42+
isError,
43+
fetchNextPage,
44+
isFetchingNextPage,
45+
} = useInfiniteQuery({
46+
...autofixSettingsQueryOptions,
47+
select: ({pages}) => pages.flatMap(page => page.json),
48+
});
49+
50+
// Auto-fetch each page, one at a time
51+
useEffect(() => {
52+
if (!isError && !isFetchingNextPage && hasNextPage) {
53+
fetchNextPage();
54+
}
55+
}, [hasNextPage, fetchNextPage, isError, isFetchingNextPage]);
4356

44-
const [mutationData, setMutations] = useState<
45-
Record<string, Partial<AutofixAutomationSettings>>
46-
>({});
57+
const {data: integrations, isPending: isPendingIntegrations} = useQuery({
58+
...organizationIntegrationsCodingAgents(organization),
59+
select: data => data.json.integrations ?? [],
60+
});
4761

4862
const {mutate: updateBulkAutofixAutomationSettings} =
4963
useUpdateBulkAutofixAutomationSettings({
5064
onSuccess: (_data, variables) => {
51-
const {projectIds, ...rest} = variables;
52-
setMutations(prev => {
53-
const updated = {...prev};
54-
projectIds.forEach(projectId => {
55-
updated[projectId] = {
56-
...prev[projectId],
57-
...rest,
58-
};
59-
});
60-
return updated;
65+
const {projectIds, ...updates} = variables;
66+
const projectIdSet = new Set(projectIds);
67+
68+
queryClient.setQueryData(autofixSettingsQueryOptions.queryKey, oldData => {
69+
if (!oldData) {
70+
return oldData;
71+
}
72+
return {
73+
...oldData,
74+
pages: oldData.pages.map(page => ({
75+
...page,
76+
json: page.json.map(setting =>
77+
projectIdSet.has(String(setting.projectId))
78+
? {
79+
...setting,
80+
...(updates.autofixAutomationTuning !== undefined && {
81+
autofixAutomationTuning: updates.autofixAutomationTuning,
82+
}),
83+
...(updates.automatedRunStoppingPoint !== undefined && {
84+
automatedRunStoppingPoint: updates.automatedRunStoppingPoint,
85+
}),
86+
}
87+
: setting
88+
),
89+
})),
90+
};
6191
});
92+
93+
for (const projectId of projectIds) {
94+
if (updates.autofixAutomationTuning !== undefined) {
95+
ProjectsStore.onUpdateSuccess({
96+
id: projectId,
97+
autofixAutomationTuning: updates.autofixAutomationTuning ?? undefined,
98+
});
99+
}
100+
}
62101
},
63102
});
64103

65104
const autofixSettingsByProjectId = useMemo(
66105
() =>
67106
new Map(
68-
autofixAutomationSettings.flatMap(page =>
69-
page.map(setting => [String(setting.projectId), setting])
70-
)
107+
(autofixAutomationSettings ?? []).map(setting => [
108+
String(setting.projectId),
109+
setting,
110+
])
71111
),
72112
[autofixAutomationSettings]
73113
);
@@ -179,14 +219,10 @@ export default function SeerProjectTable() {
179219
filteredProjects.map(project => (
180220
<SeerProjectTableRow
181221
key={project.id}
222+
autofixSettings={autofixSettingsByProjectId.get(project.id)}
223+
integrations={integrations ?? []}
224+
isPendingIntegrations={isPendingIntegrations}
182225
project={project}
183-
isFetchingSettings={isFetchingSettings}
184-
autofixSettings={{
185-
...getDefaultAutofixSettings(project.id),
186-
...autofixSettingsByProjectId.get(project.id),
187-
...mutationData[project.id],
188-
}}
189-
updateBulkAutofixAutomationSettings={updateBulkAutofixAutomationSettings}
190226
/>
191227
))
192228
)}
@@ -251,5 +287,6 @@ const FiltersContainer = styled('div')`
251287
`;
252288

253289
const SimpleTableWithColumns = styled(SimpleTable)`
254-
grid-template-columns: max-content 1fr repeat(4, max-content);
290+
grid-template-columns: 3fr minmax(300px, 1fr) repeat(2, max-content);
291+
overflow: visible;
255292
`;

0 commit comments

Comments
 (0)