Skip to content

Commit 6cd9b9f

Browse files
authored
ref(tsc): repos endpoint to apiOptions (#112926)
1 parent 005dfa4 commit 6cd9b9f

File tree

10 files changed

+120
-124
lines changed

10 files changed

+120
-124
lines changed

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

Lines changed: 0 additions & 61 deletions
This file was deleted.

static/app/components/repositories/useBulkUpdateRepositorySettings.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {getRepositoryWithSettingsQueryKey} from 'sentry/components/repositories/useRepositoryWithSettings';
2-
import type {RepositoryWithSettings} from 'sentry/types/integrations';
2+
import type {Repository, RepositoryWithSettings} from 'sentry/types/integrations';
3+
import {apiOptions} from 'sentry/utils/api/apiOptions';
34
import {
45
fetchMutation,
56
useMutation,
@@ -45,7 +46,22 @@ export function useBulkUpdateRepositorySettings(
4546
...options,
4647
onSettled: (data, error, variables, onMutateResult, context) => {
4748
queryClient.invalidateQueries({
48-
queryKey: [`/organizations/${organization.slug}/repos/`],
49+
queryKey: apiOptions.as<Repository[]>()(
50+
'/organizations/$organizationIdOrSlug/repos/',
51+
{
52+
path: {organizationIdOrSlug: organization.slug},
53+
staleTime: 0,
54+
}
55+
).queryKey,
56+
});
57+
queryClient.invalidateQueries({
58+
queryKey: apiOptions.asInfinite<Repository[]>()(
59+
'/organizations/$organizationIdOrSlug/repos/',
60+
{
61+
path: {organizationIdOrSlug: organization.slug},
62+
staleTime: 0,
63+
}
64+
).queryKey,
4965
});
5066
(data ?? []).forEach(repo => {
5167
const queryKey = getRepositoryWithSettingsQueryKey(organization, repo.id);

static/app/utils/repositories/repoQueryOptions.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
1+
import type {InfiniteData} from '@tanstack/react-query';
2+
13
import type {Repository, RepositoryWithSettings} from 'sentry/types/integrations';
24
import type {Organization} from 'sentry/types/organization';
5+
import type {ApiResponse} from 'sentry/utils/api/apiFetch';
36
import {apiOptions} from 'sentry/utils/api/apiOptions';
47
import {encodeSort} from 'sentry/utils/discover/eventView';
58
import type {Sort} from 'sentry/utils/discover/fields';
69

10+
/**
11+
* Select helper that flattens infinite pages and deduplicates repositories
12+
* by `externalId`. Use as the `select` option with `useInfiniteQuery`.
13+
*/
14+
export function selectUniqueRepos(data: InfiniteData<ApiResponse<Repository[]>>) {
15+
const uniqueReposMap = new Map<string, Repository>();
16+
for (const page of data.pages) {
17+
for (const repo of page.json) {
18+
if (repo.externalId && !uniqueReposMap.has(repo.externalId)) {
19+
uniqueReposMap.set(repo.externalId, repo);
20+
}
21+
}
22+
}
23+
return Array.from(uniqueReposMap.values());
24+
}
25+
726
export function organizationRepositoriesInfiniteOptions({
827
organization,
928
query,
Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import type {Repository} from 'sentry/types/integrations';
2-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
3-
import {useApiQuery, type ApiQueryKey} from 'sentry/utils/queryClient';
1+
import {useQuery} from '@tanstack/react-query';
42

5-
function getRepositoriesQueryKey({orgSlug}: {orgSlug: string}): ApiQueryKey {
6-
return [
7-
getApiUrl('/organizations/$organizationIdOrSlug/repos/', {
8-
path: {organizationIdOrSlug: orgSlug},
9-
}),
10-
];
11-
}
3+
import type {Repository} from 'sentry/types/integrations';
4+
import {apiOptions} from 'sentry/utils/api/apiOptions';
125

136
export function useRepositories({orgSlug}: {orgSlug: string}) {
14-
return useApiQuery<Repository[]>(getRepositoriesQueryKey({orgSlug}), {
15-
staleTime: Infinity,
16-
});
7+
return useQuery(
8+
apiOptions.as<Repository[]>()('/organizations/$organizationIdOrSlug/repos/', {
9+
path: {organizationIdOrSlug: orgSlug},
10+
staleTime: Infinity,
11+
})
12+
);
1713
}

static/app/views/onboarding/components/useScmRepoSelection.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import type {
99
Repository,
1010
} from 'sentry/types/integrations';
1111
import {RepositoryStatus} from 'sentry/types/integrations';
12-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
13-
import type {ApiQueryKey} from 'sentry/utils/queryClient';
14-
import {fetchDataQuery, fetchMutation, useQueryClient} from 'sentry/utils/queryClient';
12+
import {apiOptions} from 'sentry/utils/api/apiOptions';
13+
import {fetchMutation, useQueryClient} from 'sentry/utils/queryClient';
1514
import {useOrganization} from 'sentry/utils/useOrganization';
1615

1716
interface UseScmRepoSelectionOptions {
@@ -64,23 +63,19 @@ export function useScmRepoSelection({
6463
// pagination issues with the full list.
6564
setBusy(true);
6665
try {
67-
const queryKey: ApiQueryKey = [
68-
getApiUrl('/organizations/$organizationIdOrSlug/repos/', {
69-
path: {organizationIdOrSlug: organization.slug},
70-
}),
66+
const reposQueryOptions = apiOptions.as<Repository[]>()(
67+
'/organizations/$organizationIdOrSlug/repos/',
7168
{
69+
path: {organizationIdOrSlug: organization.slug},
7270
query: {
7371
status: 'active',
7472
integration_id: integration.id,
7573
query: repo.identifier,
7674
},
77-
},
78-
];
79-
const [matches] = await queryClient.fetchQuery({
80-
queryKey,
81-
queryFn: fetchDataQuery<Repository[]>,
82-
staleTime: 0,
83-
});
75+
staleTime: 0,
76+
}
77+
);
78+
const matches = (await queryClient.fetchQuery(reposQueryOptions)).json;
8479
// The query param above is an icontains filter to narrow results
8580
// and avoid pagination. The exact match here uses Repository.name
8681
// against IntegrationRepository.identifier — the same comparison the

static/app/views/settings/organizationIntegrations/integrationRepos.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Fragment, useState} from 'react';
2+
import {useQuery} from '@tanstack/react-query';
23

34
import {Alert} from '@sentry/scraps/alert';
45
import {LinkButton} from '@sentry/scraps/button';
@@ -15,8 +16,7 @@ import {IconCommit} from 'sentry/icons';
1516
import {t} from 'sentry/locale';
1617
import {RepositoryStore} from 'sentry/stores/repositoryStore';
1718
import type {Integration, Repository} from 'sentry/types/integrations';
18-
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
19-
import {useApiQuery} from 'sentry/utils/queryClient';
19+
import {apiOptions, selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
2020
import {useLocation} from 'sentry/utils/useLocation';
2121
import {useOrganization} from 'sentry/utils/useOrganization';
2222

@@ -34,31 +34,24 @@ export function IntegrationRepos(props: Props) {
3434
const {integration} = props;
3535
const organization = useOrganization();
3636
const location = useLocation();
37-
const ENDPOINT = getApiUrl('/organizations/$organizationIdOrSlug/repos/', {
38-
path: {organizationIdOrSlug: organization.slug},
39-
});
4037

4138
const {
42-
data: fetchedItemList,
39+
data: reposResponse,
4340
isPending,
4441
isError,
4542
refetch,
46-
getResponseHeader,
47-
} = useApiQuery<Repository[]>(
48-
[
49-
ENDPOINT,
50-
{
51-
query: {
52-
status: 'active',
53-
integration_id: integration.id,
54-
cursor: location.query.cursor,
55-
},
43+
} = useQuery({
44+
...apiOptions.as<Repository[]>()('/organizations/$organizationIdOrSlug/repos/', {
45+
path: {organizationIdOrSlug: organization.slug},
46+
query: {
47+
status: 'active',
48+
integration_id: integration.id,
49+
cursor: location.query.cursor,
5650
},
57-
],
58-
{
5951
staleTime: 0,
60-
}
61-
);
52+
}),
53+
select: selectJsonWithHeaders,
54+
});
6255
const [itemListState, setItemList] = useState<Repository[]>([]);
6356

6457
if (isPending) {
@@ -69,6 +62,7 @@ export function IntegrationRepos(props: Props) {
6962
return <LoadingError onRetry={refetch} />;
7063
}
7164

65+
const fetchedItemList = reposResponse?.json ?? [];
7266
const itemList = itemListState.length ? itemListState : fetchedItemList;
7367

7468
// Called by row to signal repository change.
@@ -91,7 +85,7 @@ export function IntegrationRepos(props: Props) {
9185
setItemList([...itemList, repo]);
9286
};
9387

94-
const itemListPageLinks = getResponseHeader?.('Link') ?? undefined;
88+
const itemListPageLinks = reposResponse?.headers.Link;
9589

9690
return (
9791
<Fragment>

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {Fragment, useCallback, useMemo, useRef, useState, type ChangeEvent} from 'react';
22
import styled from '@emotion/styled';
3+
import {useInfiniteQuery} from '@tanstack/react-query';
34
import {useVirtualizer} from '@tanstack/react-virtual';
45

56
import {Alert} from '@sentry/scraps/alert';
@@ -9,10 +10,14 @@ import {Flex, Stack} from '@sentry/scraps/layout';
910
import {Link} from '@sentry/scraps/link';
1011

1112
import type {ModalRenderProps} from 'sentry/actionCreators/modal';
12-
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
1313
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
1414
import {IconSearch} from 'sentry/icons';
1515
import {t, tct, tn} from 'sentry/locale';
16+
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
17+
import {
18+
organizationRepositoriesInfiniteOptions,
19+
selectUniqueRepos,
20+
} from 'sentry/utils/repositories/repoQueryOptions';
1621
import {useOrganization} from 'sentry/utils/useOrganization';
1722
import {MAX_REPOS_LIMIT} from 'sentry/views/settings/projectSeer/constants';
1823

@@ -37,10 +42,14 @@ export function AddAutofixRepoModal({
3742
Footer,
3843
closeModal,
3944
}: Props) {
40-
const {data: repositories, isFetching: isFetchingRepositories} =
41-
useOrganizationRepositories();
42-
4345
const organization = useOrganization();
46+
47+
const repositoriesQuery = useInfiniteQuery({
48+
...organizationRepositoriesInfiniteOptions({organization, query: {per_page: 100}}),
49+
select: selectUniqueRepos,
50+
});
51+
useFetchAllPages({result: repositoriesQuery});
52+
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;
4453
const [modalSearchQuery, setModalSearchQuery] = useState('');
4554
const [showMaxLimitAlert, setShowMaxLimitAlert] = useState(false);
4655
const [modalSelectedRepoIds, setModalSelectedRepoIds] =

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {useCallback, useEffect, useMemo, useState} from 'react';
22
import {useTheme} from '@emotion/react';
33
import styled from '@emotion/styled';
4+
import {useInfiniteQuery} from '@tanstack/react-query';
45

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

1112
import {openModal} from 'sentry/actionCreators/modal';
1213
import {DropdownMenu} from 'sentry/components/dropdownMenu';
13-
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
1414
import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
1515
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
1616
import type {
@@ -25,6 +25,11 @@ import {IconAdd} from 'sentry/icons';
2525
import {t, tct} from 'sentry/locale';
2626
import {PluginIcon} from 'sentry/plugins/components/pluginIcon';
2727
import type {Project} from 'sentry/types/project';
28+
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
29+
import {
30+
organizationRepositoriesInfiniteOptions,
31+
selectUniqueRepos,
32+
} from 'sentry/utils/repositories/repoQueryOptions';
2833
import {useOrganization} from 'sentry/utils/useOrganization';
2934

3035
import {AddAutofixRepoModal} from './addAutofixRepoModal';
@@ -38,8 +43,12 @@ interface ProjectSeerProps {
3843
export function AutofixRepositories({project}: ProjectSeerProps) {
3944
const theme = useTheme();
4045
const organization = useOrganization();
41-
const {data: repositories, isFetching: isFetchingRepositories} =
42-
useOrganizationRepositories();
46+
const repositoriesQuery = useInfiniteQuery({
47+
...organizationRepositoriesInfiniteOptions({organization, query: {per_page: 100}}),
48+
select: selectUniqueRepos,
49+
});
50+
useFetchAllPages({result: repositoriesQuery});
51+
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;
4352
const {
4453
preference,
4554
codeMappingRepos,

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {useMemo} from 'react';
22
import styled from '@emotion/styled';
3+
import {useInfiniteQuery} from '@tanstack/react-query';
34
import seerConfigBug1 from 'getsentry-images/spot/seer-config-bug-1.svg';
45

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

1011
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
1112
import {openModal} from 'sentry/actionCreators/modal';
12-
import {useOrganizationRepositories} from 'sentry/components/events/autofix/preferences/hooks/useOrganizationRepositories';
1313
import {useUpdateProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences';
1414
import type {
1515
ProjectSeerPreferences,
@@ -22,6 +22,11 @@ import {IconAdd} from 'sentry/icons/iconAdd';
2222
import {t, tct} from 'sentry/locale';
2323
import type {Organization} from 'sentry/types/organization';
2424
import type {Project} from 'sentry/types/project';
25+
import {useFetchAllPages} from 'sentry/utils/api/apiFetch';
26+
import {
27+
organizationRepositoriesInfiniteOptions,
28+
selectUniqueRepos,
29+
} from 'sentry/utils/repositories/repoQueryOptions';
2530
import {useOrganization} from 'sentry/utils/useOrganization';
2631
import {AddAutofixRepoModal} from 'sentry/views/settings/projectSeer/addAutofixRepoModal';
2732

@@ -58,8 +63,12 @@ const getTableHeaders = (organization: Organization): React.ReactNode[] => [
5863
export function AutofixRepositories({canWrite, preference, project}: Props) {
5964
const organization = useOrganization();
6065

61-
const {data: repositories, isFetching: isFetchingRepositories} =
62-
useOrganizationRepositories();
66+
const repositoriesQuery = useInfiniteQuery({
67+
...organizationRepositoriesInfiniteOptions({organization, query: {per_page: 100}}),
68+
select: selectUniqueRepos,
69+
});
70+
useFetchAllPages({result: repositoriesQuery});
71+
const {data: repositories, isFetching: isFetchingRepositories} = repositoriesQuery;
6372

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

0 commit comments

Comments
 (0)