@@ -2,30 +2,41 @@ import {useMemo, useState} from 'react';
22
33import type { IntegrationRepository , Repository } from 'sentry/types/integrations' ;
44import { getApiUrl } from 'sentry/utils/api/getApiUrl' ;
5- import { fetchDataQuery , useQuery } from 'sentry/utils/queryClient' ;
5+ import { fetchDataQuery , useInfiniteApiQuery , useQuery } from 'sentry/utils/queryClient' ;
66import { useDebouncedValue } from 'sentry/utils/useDebouncedValue' ;
77import { useOrganization } from 'sentry/utils/useOrganization' ;
88
99interface ScmRepoSearchResult {
1010 repos : IntegrationRepository [ ] ;
1111}
1212
13+ const PER_PAGE = 100 ;
14+
1315export function useScmRepoSearch ( integrationId : string , selectedRepo ?: Repository ) {
1416 const organization = useOrganization ( ) ;
1517 const [ search , setSearch ] = useState ( '' ) ;
1618 const debouncedSearch = useDebouncedValue ( search , 200 ) ;
1719
20+ const reposUrl = getApiUrl (
21+ '/organizations/$organizationIdOrSlug/integrations/$integrationId/repos/' ,
22+ {
23+ path : {
24+ organizationIdOrSlug : organization . slug ,
25+ integrationId,
26+ } ,
27+ }
28+ ) ;
29+
30+ // Browse: paginated fetch that fires on mount, pre-populates the dropdown
31+ const browseQuery = useInfiniteApiQuery < ScmRepoSearchResult > ( {
32+ queryKey : [ { infinite : true , version : 'v1' } , reposUrl , { query : { per_page : PER_PAGE } } ] ,
33+ staleTime : 30_000 ,
34+ } ) ;
35+
36+ // Search: fires when user types, returns full filtered result set
1837 const searchQuery = useQuery ( {
1938 queryKey : [
20- getApiUrl (
21- '/organizations/$organizationIdOrSlug/integrations/$integrationId/repos/' ,
22- {
23- path : {
24- organizationIdOrSlug : organization . slug ,
25- integrationId,
26- } ,
27- }
28- ) ,
39+ reposUrl ,
2940 { method : 'GET' , query : { search : debouncedSearch , accessibleOnly : true } } ,
3041 ] as const ,
3142 queryFn : async context => {
@@ -37,11 +48,26 @@ export function useScmRepoSearch(integrationId: string, selectedRepo?: Repositor
3748 enabled : ! ! debouncedSearch ,
3849 } ) ;
3950
51+ const isSearching = ! ! debouncedSearch ;
52+
53+ // Flatten paginated browse results into a single list
54+ const browseRepos = useMemo (
55+ ( ) => browseQuery . data ?. pages . flatMap ( page => page [ 0 ] . repos ) ?? [ ] ,
56+ [ browseQuery . data ]
57+ ) ;
58+
59+ const searchRepos = useMemo (
60+ ( ) => searchQuery . data ?. [ 0 ] ?. repos ?? [ ] ,
61+ [ searchQuery . data ]
62+ ) ;
63+
64+ const activeRepos = isSearching ? searchRepos : browseRepos ;
65+
4066 const selectedRepoSlug = selectedRepo ?. externalSlug ;
4167
4268 const { reposByIdentifier, dropdownItems} = useMemo (
4369 ( ) =>
44- ( searchQuery . data ?. [ 0 ] ?. repos ?? [ ] ) . reduce < {
70+ activeRepos . reduce < {
4571 dropdownItems : Array < {
4672 disabled : boolean ;
4773 label : string ;
@@ -63,15 +89,19 @@ export function useScmRepoSearch(integrationId: string, selectedRepo?: Repositor
6389 dropdownItems : [ ] ,
6490 }
6591 ) ,
66- [ searchQuery . data , selectedRepoSlug ]
92+ [ activeRepos , selectedRepoSlug ]
6793 ) ;
6894
6995 return {
7096 reposByIdentifier,
7197 dropdownItems,
72- isFetching : searchQuery . isFetching ,
73- isError : searchQuery . isError ,
98+ isFetching : isSearching ? searchQuery . isFetching : browseQuery . isFetching ,
99+ isError : isSearching ? searchQuery . isError : browseQuery . isError ,
74100 debouncedSearch,
75101 setSearch,
102+ // Infinite scroll support
103+ hasNextPage : ! isSearching && ( browseQuery . hasNextPage ?? false ) ,
104+ fetchNextPage : browseQuery . fetchNextPage ,
105+ isFetchingNextPage : browseQuery . isFetchingNextPage ,
76106 } ;
77107}
0 commit comments