Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
97160fd
ref(seer): Combine AutofixRepositories components into one
ryan953 Jun 14, 2026
a3d8313
test(seer): Update projectSeer tests for combined AutofixRepositories…
ryan953 Jun 14, 2026
f0050e8
ref(seer): Rewrite AutofixRepositories using the new /seer/repos/ end…
ryan953 Jun 15, 2026
dfef839
add in the missing isntructions textarea, and tidy things
ryan953 Jun 15, 2026
b067ee1
remove unused export of BranchOverride
ryan953 Jun 15, 2026
d295dbc
Merge branch 'master' into ryan953/ref-autofix-repos
ryan953 Jun 15, 2026
900ea1a
Fix "Onboarding drops existing repositories" bot review
ryan953 Jun 15, 2026
9a44a29
Fix "Repository limit no longer enforced" bot review comment
ryan953 Jun 15, 2026
6965a3f
fix not-found error and select alignment in the modal
ryan953 Jun 15, 2026
432ffa3
fix(seer): Fix silent invalidateQueries no-op in addRepo mutation
ryan953 Jun 15, 2026
f054409
fix "Done triggers empty repo POST" bot review comment
ryan953 Jun 15, 2026
d2fd488
Merge branch 'master' into ryan953/ref-autofix-repos
ryan953 Jun 15, 2026
212cdc2
fix(seer): update test mocks to use new /seer/repos/ endpoints
ryan953 Jun 15, 2026
5e6d47d
fix(seer): Split repo name on first slash only for optimistic updates
ryan953 Jun 15, 2026
ad8b2b3
fix(seer): restore 'Default branch' placeholder casing
Jun 15, 2026
3c4adec
fix(seer): move instructions AutoSaveForm outside branchOverrides for…
billyvg Jun 16, 2026
074c94e
:hammer_and_wrench: apply pre-commit fixes
getsantry[bot] Jun 16, 2026
8a15aa9
Count hiddenExternalIds.length + prev.length against MAX_REPOS_LIMIT
ryan953 Jun 16, 2026
7674acf
fix(seer): add tab/blur after typing in test to trigger AutoSaveForm …
billyvg Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions static/app/components/core/form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
withForm,
} from './scrapsForm';
export {AutoSaveForm} from './autoSaveForm';
export {AutoSaveContextProvider} from './autoSaveContext';
export {FieldGroup} from './layout/fieldGroup';
export {FormSearch} from './FormSearch';
export {FORM_FIELD_REGISTRY} from './generatedFieldRegistry';
Expand Down
8 changes: 1 addition & 7 deletions static/app/components/events/autofix/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,18 +134,12 @@ export interface AutofixRepoDefinition {
provider: string;
}

export interface BranchOverride {
interface BranchOverride {
branch_name: string;
tag_name: string;
tag_value: string;
}

export interface RepoSettings {
branch: string;
branch_overrides: BranchOverride[];
instructions: string;
}

export interface SeerRepoDefinition {
external_id: string;
name: string;
Expand Down
112 changes: 61 additions & 51 deletions static/app/components/seer/legacy/addAutofixRepoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import {Fragment, useCallback, useMemo, useRef, useState, type ChangeEvent} from 'react';
import {
Fragment,
useCallback,
useEffect,
useMemo,
useRef,
useState,
type ChangeEvent,
} from 'react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';
import {useVirtualizer} from '@tanstack/react-virtual';
Expand All @@ -12,7 +20,6 @@ import {Link} from '@sentry/scraps/link';
import type {ModalRenderProps} from 'sentry/actionCreators/modal';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {MAX_REPOS_LIMIT} from 'sentry/components/seer/legacy/constants';
import {SelectableRepoItem} from 'sentry/components/seer/legacy/selectableRepoItem';
import {IconSearch} from 'sentry/icons';
import {t, tct, tn} from 'sentry/locale';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
Expand All @@ -22,19 +29,28 @@ import {
} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';

import {SelectableRepoItem} from './selectableRepoItem';

type Props = ModalRenderProps & {
/**
* Callback function triggered when the modal is saved.
* Repositories currently selected for Autofix in the parent component.
*/
onSave: (repoIds: string[]) => void;
hiddenExternalIds: string[];

/**
* Repositories currently selected for Autofix in the parent component.
* Callback function triggered when the modal is saved.
*/
selectedRepoIds: string[];
onSave: ({
selectedExternalIds,
selectedRepoIds,
}: {
selectedExternalIds: string[];
selectedRepoIds: string[];
}) => void;
};

export function AddAutofixRepoModal({
selectedRepoIds,
hiddenExternalIds,
onSave,
Header,
Body,
Expand All @@ -49,53 +65,44 @@ export function AddAutofixRepoModal({
});
useFetchAllPages({result: repositoriesQuery});
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;

const [modalSearchQuery, setModalSearchQuery] = useState('');
const [selectedExternalIds, setSelectedExternalIds] = useState<string[]>([]);
const [showMaxLimitAlert, setShowMaxLimitAlert] = useState(false);
const [modalSelectedRepoIds, setModalSelectedRepoIds] = useState(selectedRepoIds);

const newModalSelectedRepoIds = modalSelectedRepoIds.filter(
id => !selectedRepoIds.includes(id)
);

const unselectedRepositories = useMemo(() => {
const filteredRepositories = useMemo(() => {
if (!repositories) {
return [];
}
return repositories.filter(repo => !selectedRepoIds.includes(repo.externalId));
}, [repositories, selectedRepoIds]);

const filteredModalRepositories = useMemo(() => {
let filtered = unselectedRepositories;
if (modalSearchQuery.trim()) {
const query = modalSearchQuery.toLowerCase();
filtered = unselectedRepositories.filter(repo =>
repo.name.toLowerCase().includes(query)
);
}

return filtered.filter(repo => repo.provider?.id && repo.provider.id !== 'unknown');
}, [unselectedRepositories, modalSearchQuery]);

const handleToggleRepository = useCallback((repoId: string) => {
setModalSelectedRepoIds(prev => {
if (prev.includes(repoId)) {
setShowMaxLimitAlert(false);
return prev.filter(id => id !== repoId);
const query = modalSearchQuery.trim().toLowerCase();
return repositories.filter(repo => {
if (hiddenExternalIds.includes(repo.externalId)) {
return false;
}
if (prev.length >= MAX_REPOS_LIMIT) {
setShowMaxLimitAlert(true);
return prev;
if (query && !repo.name.toLowerCase().includes(query)) {
return false;
}
return true;
});
Comment thread
ryan953 marked this conversation as resolved.
}, [repositories, hiddenExternalIds, modalSearchQuery]);

const handleToggleRepository = useCallback((externalId: string) => {
setSelectedExternalIds(prev => {
if (prev.includes(externalId)) {
return prev.filter(id => id !== externalId);
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
}
setShowMaxLimitAlert(false);
return [...prev, repoId];
return prev.length >= MAX_REPOS_LIMIT ? prev : [...prev, externalId];
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
});
}, []);

// Virtualizer setup (simplified based on docs)
useEffect(() => {
setShowMaxLimitAlert(selectedExternalIds.length >= MAX_REPOS_LIMIT);
}, [selectedExternalIds.length]);
Comment thread
cursor[bot] marked this conversation as resolved.
Comment on lines +103 to +105

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The repository limit alert does not account for existing repositories, causing silent UI blocking when the total limit is reached.
Severity: MEDIUM

Suggested Fix

Update the useEffect hook that sets the showMaxLimitAlert. The condition should be changed to selectedExternalIds.length + hiddenExternalIds.length >= MAX_REPOS_LIMIT to be consistent with the selection-blocking logic in handleToggleRepository.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: static/app/components/seer/legacy/addAutofixRepoModal.tsx#L103-L105

Potential issue: The logic to display a repository limit alert is inconsistent with the
logic that enforces the limit. The alert is triggered when `selectedExternalIds.length
>= MAX_REPOS_LIMIT`, only considering newly selected repositories. However, the
selection is blocked based on `hiddenExternalIds.length + prev.length >=
MAX_REPOS_LIMIT`, which includes already-connected repositories. This causes a situation
where a user can be blocked from selecting more repositories without any alert or
feedback, creating a confusing user experience where clicks have no effect.


const parentRef = useRef<HTMLDivElement>(null);

const rowVirtualizer = useVirtualizer({
count: filteredModalRepositories.length,
count: filteredRepositories.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 36,
overscan: 20,
Expand Down Expand Up @@ -135,9 +142,9 @@ export function AddAutofixRepoModal({
<StyledLoadingIndicator size={36} />
<LoadingMessage>{t('Loading repositories...')}</LoadingMessage>
</Stack>
) : filteredModalRepositories.length === 0 ? (
) : filteredRepositories.length === 0 ? (
<EmptyMessage>
{modalSearchQuery.trim() && unselectedRepositories.length > 0
{modalSearchQuery.trim()
? t('No matching repositories found.')
: t('All available repositories have been added.')}
Comment thread
ryan953 marked this conversation as resolved.
</EmptyMessage>
Expand All @@ -151,7 +158,7 @@ export function AddAutofixRepoModal({
}}
>
{rowVirtualizer.getVirtualItems().map(virtualItem => {
const repo = filteredModalRepositories[virtualItem.index]!;
const repo = filteredRepositories[virtualItem.index]!;
return (
<div
key={virtualItem.key}
Expand All @@ -166,7 +173,7 @@ export function AddAutofixRepoModal({
>
<SelectableRepoItem
repo={repo}
isSelected={modalSelectedRepoIds.includes(repo.externalId)}
isSelected={selectedExternalIds.includes(repo.externalId)}
onToggle={handleToggleRepository}
/>
</div>
Expand All @@ -191,16 +198,19 @@ export function AddAutofixRepoModal({
<Button
variant="primary"
onClick={() => {
onSave(modalSelectedRepoIds);
const selectedRepoIds = selectedExternalIds
.map(
externalId =>
repositories?.find(repo => repo.externalId === externalId)?.id
)
.filter<string>(value => value !== undefined);
Comment thread
sentry[bot] marked this conversation as resolved.

onSave({selectedExternalIds, selectedRepoIds});
Comment thread
ryan953 marked this conversation as resolved.
closeModal();
}}
>
{newModalSelectedRepoIds.length > 0
? tn(
'Add %s Repository',
'Add %s Repositories',
newModalSelectedRepoIds.length
)
{selectedExternalIds.length > 0
? tn('Add %s Repository', 'Add %s Repositories', selectedExternalIds.length)
: t('Done')}
</Button>
</Flex>
Expand Down
Loading
Loading