Skip to content

Commit e161280

Browse files
committed
feat(onboarding): Pre-populate repo selector with full repo list
Load the complete list of accessible repositories on mount so users can browse and select without typing first. Search still narrows results server-side when the user types. Previously the dropdown was empty until a search query was entered, requiring users to know what to search for. Refs VDY-46
1 parent 40cdd23 commit e161280

File tree

2 files changed

+17
-38
lines changed

2 files changed

+17
-38
lines changed

static/app/views/onboarding/components/scmRepoSelector.tsx

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
99
import {useOrganization} from 'sentry/utils/useOrganization';
1010

1111
import {ScmSearchControl} from './scmSearchControl';
12+
import {ScmVirtualizedMenuList} from './scmVirtualizedMenuList';
1213
import {useScmRepoSearch} from './useScmRepoSearch';
1314
import {useScmRepoSelection} from './useScmRepoSelection';
1415

@@ -20,14 +21,10 @@ export function ScmRepoSelector({integration}: ScmRepoSelectorProps) {
2021
const organization = useOrganization();
2122
const {selectedRepository, setSelectedRepository, clearDerivedState} =
2223
useOnboardingContext();
23-
const {
24-
reposByIdentifier,
25-
dropdownItems,
26-
isFetching,
27-
isError,
28-
debouncedSearch,
29-
setSearch,
30-
} = useScmRepoSearch(integration.id, selectedRepository);
24+
const {reposByIdentifier, dropdownItems, isFetching, isError} = useScmRepoSearch(
25+
integration.id,
26+
selectedRepository
27+
);
3128

3229
const {busy, handleSelect, handleRemove} = useScmRepoSelection({
3330
integration,
@@ -58,7 +55,6 @@ export function ScmRepoSelector({integration}: ScmRepoSelectorProps) {
5855
clearDerivedState();
5956

6057
if (option === null) {
61-
setSearch('');
6258
handleRemove();
6359
} else {
6460
const repo = reposByIdentifier.get(option.value);
@@ -75,14 +71,11 @@ export function ScmRepoSelector({integration}: ScmRepoSelectorProps) {
7571

7672
function noOptionsMessage() {
7773
if (isError) {
78-
return t('Failed to search repositories. Please try again.');
79-
}
80-
if (debouncedSearch) {
81-
return t(
82-
'No repositories found. Check your installation permissions to ensure your integration has access.'
83-
);
74+
return t('Failed to load repositories. Please try again.');
8475
}
85-
return t('Type to search repositories');
76+
return t(
77+
'No repositories found. Check your installation permissions to ensure your integration has access.'
78+
);
8679
}
8780

8881
return (
@@ -91,19 +84,12 @@ export function ScmRepoSelector({integration}: ScmRepoSelectorProps) {
9184
options={options}
9285
value={selectedRepository?.externalSlug ?? null}
9386
onChange={handleChange}
94-
onInputChange={(value, actionMeta) => {
95-
if (actionMeta.action === 'input-change') {
96-
setSearch(value);
97-
}
98-
}}
99-
// Disable client-side filtering; search is handled server-side.
100-
filterOption={() => true}
10187
noOptionsMessage={noOptionsMessage}
10288
isLoading={isFetching}
10389
isDisabled={busy}
10490
clearable
10591
searchable
106-
components={{Control: ScmSearchControl}}
92+
components={{Control: ScmSearchControl, MenuList: ScmVirtualizedMenuList}}
10793
/>
10894
);
10995
}

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

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

33
import type {IntegrationRepository, Repository} from 'sentry/types/integrations';
44
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
55
import {fetchDataQuery, useQuery} from 'sentry/utils/queryClient';
6-
import {useDebouncedValue} from 'sentry/utils/useDebouncedValue';
76
import {useOrganization} from 'sentry/utils/useOrganization';
87

98
interface ScmRepoSearchResult {
@@ -12,10 +11,8 @@ interface ScmRepoSearchResult {
1211

1312
export function useScmRepoSearch(integrationId: string, selectedRepo?: Repository) {
1413
const organization = useOrganization();
15-
const [search, setSearch] = useState('');
16-
const debouncedSearch = useDebouncedValue(search, 200);
1714

18-
const searchQuery = useQuery({
15+
const reposQuery = useQuery({
1916
queryKey: [
2017
getApiUrl(
2118
'/organizations/$organizationIdOrSlug/integrations/$integrationId/repos/',
@@ -26,22 +23,20 @@ export function useScmRepoSearch(integrationId: string, selectedRepo?: Repositor
2623
},
2724
}
2825
),
29-
{method: 'GET', query: {search: debouncedSearch, accessibleOnly: true}},
26+
{method: 'GET', query: {accessibleOnly: true}},
3027
] as const,
3128
queryFn: async context => {
3229
return fetchDataQuery<ScmRepoSearchResult>(context);
3330
},
3431
retry: 0,
3532
staleTime: 20_000,
36-
placeholderData: previousData => (debouncedSearch ? previousData : undefined),
37-
enabled: !!debouncedSearch,
3833
});
3934

4035
const selectedRepoSlug = selectedRepo?.externalSlug;
4136

4237
const {reposByIdentifier, dropdownItems} = useMemo(
4338
() =>
44-
(searchQuery.data?.[0]?.repos ?? []).reduce<{
39+
(reposQuery.data?.[0]?.repos ?? []).reduce<{
4540
dropdownItems: Array<{
4641
disabled: boolean;
4742
label: string;
@@ -63,15 +58,13 @@ export function useScmRepoSearch(integrationId: string, selectedRepo?: Repositor
6358
dropdownItems: [],
6459
}
6560
),
66-
[searchQuery.data, selectedRepoSlug]
61+
[reposQuery.data, selectedRepoSlug]
6762
);
6863

6964
return {
7065
reposByIdentifier,
7166
dropdownItems,
72-
isFetching: searchQuery.isFetching,
73-
isError: searchQuery.isError,
74-
debouncedSearch,
75-
setSearch,
67+
isFetching: reposQuery.isFetching,
68+
isError: reposQuery.isError,
7669
};
7770
}

0 commit comments

Comments
 (0)