Small style changes to repo picker (#6013)

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Graham Neubig <neubig@gmail.com>
Co-authored-by: sp.wack <83104063+amanape@users.noreply.github.com>
This commit is contained in:
Robert Brennan 2025-01-03 15:44:30 -05:00 committed by GitHub
parent 825a9ba893
commit 761a574b09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 84 additions and 67 deletions

View File

@ -14,7 +14,8 @@ describe("GitHubRepositorySelector", () => {
<GitHubRepositorySelector
onInputChange={onInputChangeMock}
onSelect={onSelectMock}
repositories={[]}
publicRepositories={[]}
userRepositories={[]}
/>,
);
@ -36,7 +37,8 @@ describe("GitHubRepositorySelector", () => {
<GitHubRepositorySelector
onInputChange={onInputChangeMock}
onSelect={onSelectMock}
repositories={[]}
publicRepositories={[]}
userRepositories={[]}
/>,
);
@ -67,7 +69,8 @@ describe("GitHubRepositorySelector", () => {
<GitHubRepositorySelector
onInputChange={onInputChangeMock}
onSelect={onSelectMock}
repositories={[]}
publicRepositories={[]}
userRepositories={[]}
/>,
);

View File

@ -110,16 +110,11 @@ export const searchPublicRepositories = async (
sort: "" | "updated" | "stars" | "forks" = "stars",
order: "desc" | "asc" = "desc",
): Promise<GitHubRepository[]> => {
const sanitizedQuery = query.trim();
if (!sanitizedQuery) {
return [];
}
const response = await github.get<{ items: GitHubRepository[] }>(
"/search/repositories",
{
params: {
q: sanitizedQuery,
q: query,
per_page,
sort,
order,

View File

@ -1,46 +1,48 @@
import React from "react";
import { Autocomplete, AutocompleteItem } from "@nextui-org/react";
import {
Autocomplete,
AutocompleteItem,
AutocompleteSection,
} from "@nextui-org/react";
import { useDispatch } from "react-redux";
import posthog from "posthog-js";
import { setSelectedRepository } from "#/state/initial-query-slice";
import { useConfig } from "#/hooks/query/use-config";
interface GitHubRepositoryWithPublic extends GitHubRepository {
is_public?: boolean;
}
import { sanitizeQuery } from "#/utils/sanitize-query";
interface GitHubRepositorySelectorProps {
onInputChange: (value: string) => void;
onSelect: () => void;
repositories: GitHubRepositoryWithPublic[];
userRepositories: GitHubRepository[];
publicRepositories: GitHubRepository[];
}
export function GitHubRepositorySelector({
onInputChange,
onSelect,
repositories,
userRepositories,
publicRepositories,
}: GitHubRepositorySelectorProps) {
const { data: config } = useConfig();
const [selectedKey, setSelectedKey] = React.useState<string | null>(null);
const allRepositories: GitHubRepository[] = [
...publicRepositories.filter(
(repo) => !publicRepositories.find((r) => r.id === repo.id),
),
...userRepositories,
];
const dispatch = useDispatch();
const handleRepoSelection = (id: string | null) => {
const repo = repositories.find((r) => r.id.toString() === id);
if (!repo) return;
if (repo.id === -1000) {
window.open(
`https://github.com/apps/${config?.APP_SLUG}/installations/new`,
"_blank",
);
return;
const repo = allRepositories.find((r) => r.id.toString() === id);
if (repo) {
dispatch(setSelectedRepository(repo.full_name));
posthog.capture("repository_selected");
onSelect();
setSelectedKey(id);
}
dispatch(setSelectedRepository(repo.full_name));
posthog.capture("repository_selected");
onSelect();
setSelectedKey(id);
};
const handleClearSelection = () => {
@ -55,8 +57,8 @@ export function GitHubRepositorySelector({
name="repo"
aria-label="GitHub Repository"
placeholder="Select a GitHub project"
isVirtualized={false}
selectedKey={selectedKey}
items={repositories}
inputProps={{
classNames: {
inputWrapper:
@ -65,27 +67,61 @@ export function GitHubRepositorySelector({
}}
onSelectionChange={(id) => handleRepoSelection(id?.toString() ?? null)}
onInputChange={onInputChange}
clearButtonProps={{ onPress: handleClearSelection }}
clearButtonProps={{ onClick: handleClearSelection }}
listboxProps={{
emptyContent,
}}
defaultFilter={(textValue, inputValue) =>
!inputValue ||
sanitizeQuery(textValue).includes(sanitizeQuery(inputValue))
}
>
{(item) => (
<AutocompleteItem
data-testid="github-repo-item"
key={item.id}
value={item.id}
textValue={item.full_name}
>
<div className="flex items-center justify-between">
{item.full_name}
{item.is_public && !!item.stargazers_count && (
<span className="text-xs text-gray-400">
({item.stargazers_count})
{config?.APP_MODE === "saas" &&
config?.APP_SLUG &&
((
<AutocompleteItem key="install">
<a
href={`https://github.com/apps/${config.APP_SLUG}/installations/new`}
target="_blank"
rel="noreferrer noopener"
onClick={(e) => e.stopPropagation()}
>
Add more repositories...
</a>
</AutocompleteItem> // eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any)}
{userRepositories.length > 0 && (
<AutocompleteSection showDivider title="Your Repos">
{userRepositories.map((repo) => (
<AutocompleteItem
data-testid="github-repo-item"
key={repo.id}
value={repo.id}
className="data-[selected=true]:bg-default-100"
textValue={repo.full_name}
>
{repo.full_name}
</AutocompleteItem>
))}
</AutocompleteSection>
)}
{publicRepositories.length > 0 && (
<AutocompleteSection showDivider title="Public Repos">
{publicRepositories.map((repo) => (
<AutocompleteItem
data-testid="github-repo-item"
key={repo.id}
value={repo.id}
className="data-[selected=true]:bg-default-100"
textValue={repo.full_name}
>
{repo.full_name}
<span className="ml-1 text-gray-400">
({repo.stargazers_count || 0})
</span>
)}
</div>
</AutocompleteItem>
</AutocompleteItem>
))}
</AutocompleteSection>
)}
</Autocomplete>
);

View File

@ -11,7 +11,6 @@ import { useSearchRepositories } from "#/hooks/query/use-search-repositories";
import { useUserRepositories } from "#/hooks/query/use-user-repositories";
import { sanitizeQuery } from "#/utils/sanitize-query";
import { useDebounce } from "#/hooks/use-debounce";
import { useConfig } from "#/hooks/query/use-config";
interface GitHubRepositoriesSuggestionBoxProps {
handleSubmit: () => void;
@ -29,7 +28,6 @@ export function GitHubRepositoriesSuggestionBox({
const [searchQuery, setSearchQuery] = React.useState<string>("");
const debouncedSearchQuery = useDebounce(searchQuery, 300);
const { data: config } = useConfig();
// TODO: Use `useQueries` to fetch all repositories in parallel
const { data: appRepositories } = useAppRepositories();
const { data: userRepositories } = useUserRepositories();
@ -37,19 +35,6 @@ export function GitHubRepositoriesSuggestionBox({
sanitizeQuery(debouncedSearchQuery),
);
const saasPlaceholderRepository = React.useMemo(() => {
if (config?.APP_MODE === "saas" && config?.APP_SLUG) {
return [
{
id: -1000,
full_name: "Add more repositories...",
},
];
}
return [];
}, [config]);
const repositories =
userRepositories?.pages.flatMap((page) => page.data) ||
appRepositories?.pages.flatMap((page) => page.data) ||
@ -74,11 +59,8 @@ export function GitHubRepositoriesSuggestionBox({
<GitHubRepositorySelector
onInputChange={setSearchQuery}
onSelect={handleSubmit}
repositories={[
...saasPlaceholderRepository,
...searchedRepos,
...repositories,
]}
publicRepositories={searchedRepos}
userRepositories={repositories}
/>
) : (
<ModalButton

View File

@ -1,5 +1,6 @@
export const sanitizeQuery = (query: string) =>
query
.trim()
.replace(/https?:\/\//, "")
.replace(/github.com\//, "")
.replace(/\.git$/, "")