Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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

This file was deleted.

14 changes: 10 additions & 4 deletions static/app/views/settings/projectSeer/addAutofixRepoModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Fragment, useCallback, useMemo, useRef, useState, type ChangeEvent} from 'react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';
import {useVirtualizer} from '@tanstack/react-virtual';

import {Alert} from '@sentry/scraps/alert';
Expand All @@ -9,10 +10,11 @@ import {Flex, Stack} from '@sentry/scraps/layout';
import {Link} from '@sentry/scraps/link';

import type {ModalRenderProps} from 'sentry/actionCreators/modal';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {IconSearch} from 'sentry/icons';
import {t, tct, tn} from 'sentry/locale';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {organizationRepositoriesInfiniteOptions} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {MAX_REPOS_LIMIT} from 'sentry/views/settings/projectSeer/constants';

Expand All @@ -37,10 +39,14 @@ export function AddAutofixRepoModal({
Footer,
closeModal,
}: Props) {
const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();

const organization = useOrganization();
const result = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization}),
select: ({pages}) => pages.flatMap(page => page.json),
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.

Deduplication of repositories by externalId removed

Low Severity

The deleted useOrganizationRepositories hook deduplicated repositories by externalId, keeping only the first occurrence and filtering out repos without an externalId. The replacement select: ({pages}) => pages.flatMap(page => page.json) does plain flattening with no deduplication. If the API returns repos sharing the same externalId (e.g., connected through multiple integrations), downstream code that keys on externalId for selection, settings, and filtering could display duplicates or behave unexpectedly.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5502bfa. Configure here.

});
useFetchAllPages({result});
const {data: repositories, isFetching: isFetchingRepositories} = result;
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.

Loading state flickers between sequential page fetches

Medium Severity

Using isFetching from useInfiniteQuery as the loading guard causes the UI to flicker between page fetches. After each page completes, isFetching briefly becomes false while hasNextPage is still true, causing the loading spinner to disappear and partial data to flash before useFetchAllPages triggers the next fetch via useEffect (which runs after paint). The old useFetchSequentialPages kept isFetching=true until all pages completed. The existing integrationCodeMappings.tsx shows the correct pattern: deriving the loading state as isPending || isFetchingNextPage || (!!hasNextPage && !isError).

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5502bfa. Configure here.


const [modalSearchQuery, setModalSearchQuery] = useState('');
const [showMaxLimitAlert, setShowMaxLimitAlert] = useState(false);
const [modalSelectedRepoIds, setModalSelectedRepoIds] =
Expand Down
12 changes: 9 additions & 3 deletions static/app/views/settings/projectSeer/autofixRepositories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useCallback, useEffect, useMemo, useState} from 'react';
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';

import {Alert} from '@sentry/scraps/alert';
import {Button} from '@sentry/scraps/button';
Expand All @@ -10,7 +11,6 @@ import {Tooltip} from '@sentry/scraps/tooltip';

import {openModal} from 'sentry/actionCreators/modal';
import {DropdownMenu} from 'sentry/components/dropdownMenu';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
import type {
Expand All @@ -25,6 +25,8 @@ import {IconAdd} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import {PluginIcon} from 'sentry/plugins/components/pluginIcon';
import type {Project} from 'sentry/types/project';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {organizationRepositoriesInfiniteOptions} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';

import {AddAutofixRepoModal} from './addAutofixRepoModal';
Expand All @@ -38,8 +40,12 @@ interface ProjectSeerProps {
export function AutofixRepositories({project}: ProjectSeerProps) {
const theme = useTheme();
const organization = useOrganization();
const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();
const result = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization}),
select: ({pages}) => pages.flatMap(page => page.json),
});
useFetchAllPages({result});
const {data: repositories, isFetching: isFetchingRepositories} = result;
Comment on lines +43 to +48
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 component doesn't handle the isError state from the API call. If the fetch fails, the UI will show a loading indicator indefinitely without displaying an error.
Severity: HIGH

Suggested Fix

In both autofixRepositories.tsx and addAutofixRepoModal.tsx, destructure isError from the useInfiniteQuery result. Add UI logic to check for isError and display an appropriate error message or error state to the user instead of the loading indicator when the API call fails.

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/views/settings/projectSeer/autofixRepositories.tsx#L43-L48

Potential issue: The refactoring to use `useInfiniteQuery` and `useFetchAllPages` in
`autofixRepositories.tsx` and `addAutofixRepoModal.tsx` omits error handling. The
components do not check the `result.isError` property. If an API error occurs during
pagination, `useFetchAllPages` will stop fetching, but the UI will not be notified of
the failure. This results in the component remaining in a perpetual loading state,
providing no feedback to the user that something went wrong or a way to retry. This
contrasts with established patterns elsewhere in the codebase where `isError` is
explicitly handled to show an error state.

Did we get this right? 👍 / 👎 to inform future reviews.

const {
preference,
codeMappingRepos,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {useCallback, useMemo} from 'react';
import styled from '@emotion/styled';
import {useInfiniteQuery} from '@tanstack/react-query';
import seerConfigBug1 from 'getsentry-images/spot/seer-config-bug-1.svg';

import {Button} from '@sentry/scraps/button';
Expand All @@ -9,7 +10,6 @@ import {Heading} from '@sentry/scraps/text';

import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import {openModal} from 'sentry/actionCreators/modal';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
import type {
ProjectSeerPreferences,
Expand All @@ -22,6 +22,8 @@ import {IconAdd} from 'sentry/icons/iconAdd';
import {t, tct} from 'sentry/locale';
import type {Organization} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {organizationRepositoriesInfiniteOptions} from 'sentry/utils/repositories/repoQueryOptions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {AddAutofixRepoModal} from 'sentry/views/settings/projectSeer/addAutofixRepoModal';

Expand Down Expand Up @@ -57,9 +59,12 @@ const getTableHeaders = (organization: Organization): React.ReactNode[] => [

export function AutofixRepositories({canWrite, preference, project}: Props) {
const organization = useOrganization();

const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();
const result = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization}),
select: ({pages}) => pages.flatMap(page => page.json),
});
useFetchAllPages({result});
const {data: repositories, isFetching: isFetchingRepositories} = result;

const {mutate: updateProjectSeerPreferences} = useUpdateProjectSeerPreferences(project);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
import {openModal} from 'sentry/actionCreators/modal';
import {hasEveryAccess} from 'sentry/components/acl/access';
import {ClippedBox} from 'sentry/components/clippedBox';
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
import type {SeerRepoDefinition} from 'sentry/components/events/autofix/types';
Expand All @@ -35,8 +34,10 @@ import {IconChevron, IconSearch} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import type {Repository} from 'sentry/types/integrations';
import type {Project} from 'sentry/types/project';
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
import {makeDetailedProjectQueryKey} from 'sentry/utils/project/useDetailedProject';
import {useQueryClient} from 'sentry/utils/queryClient';
import {useInfiniteQuery, useQueryClient} from 'sentry/utils/queryClient';
import {organizationRepositoriesInfiniteOptions} from 'sentry/utils/repositories/repoQueryOptions';
import {useApi} from 'sentry/utils/useApi';
import {useNavigate} from 'sentry/utils/useNavigate';
import {useOrganization} from 'sentry/utils/useOrganization';
Expand Down Expand Up @@ -202,8 +203,13 @@ function ProjectsWithoutRepos({
onProjectSuccess: (projectId: string) => void;
projects: Project[];
}) {
const {data: repositories, isFetching: isFetchingRepositories} =
useOrganizationRepositories();
const organization = useOrganization();
const result = useInfiniteQuery({
...organizationRepositoriesInfiniteOptions({organization}),
select: ({pages}) => pages.flatMap(page => page.json),
});
useFetchAllPages({result});
const {data: repositories, isFetching: isFetchingRepositories} = result;

const [projectStates, setProjectStates] = useState<ProjectStateMap>({});
const [successfullyConnectedProjects, setSuccessfullyConnectedProjects] = useState(
Expand Down
Loading