fix(frontend): org level microagents not appearing (microagent management) (#11218)

This commit is contained in:
Hiep Le 2025-10-02 22:19:02 +07:00 committed by GitHub
parent d3395172f8
commit c932cd0815
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 161 additions and 28 deletions

View File

@ -157,8 +157,52 @@ describe("MicroagentManagement", () => {
owner_type: "organization",
pushed_at: "2021-10-06T12:00:00Z",
},
{
id: "7",
full_name: "user/gitlab-repo/openhands-config",
git_provider: "gitlab",
is_public: true,
owner_type: "user",
pushed_at: "2021-10-07T12:00:00Z",
},
{
id: "8",
full_name: "org/gitlab-org-repo/openhands-config",
git_provider: "gitlab",
is_public: true,
owner_type: "organization",
pushed_at: "2021-10-08T12:00:00Z",
},
];
// Helper function to filter repositories with OpenHands suffixes
const getRepositoriesWithOpenHandsSuffix = (
repositories: GitRepository[],
) => {
return repositories.filter(
(repo) =>
repo.full_name.endsWith("/.openhands") ||
repo.full_name.endsWith("/openhands-config"),
);
};
// Helper functions for mocking search repositories
const mockSearchRepositoriesWithData = (data: GitRepository[]) => {
mockUseSearchRepositories.mockReturnValue({
data,
isLoading: false,
isError: false,
});
};
const mockSearchRepositoriesEmpty = () => {
mockUseSearchRepositories.mockReturnValue({
data: [],
isLoading: false,
isError: false,
});
};
const mockMicroagents: RepositoryMicroagent[] = [
{
name: "test-microagent-1",
@ -265,11 +309,11 @@ describe("MicroagentManagement", () => {
isError: false,
});
mockUseSearchRepositories.mockReturnValue({
data: [],
isLoading: false,
isError: false,
});
// Mock the search repositories hook to return repositories with OpenHands suffixes
const mockSearchResults =
getRepositoriesWithOpenHandsSuffix(mockRepositories);
mockSearchRepositoriesWithData(mockSearchResults);
// Setup default mock for retrieveUserGitRepositories
vi.spyOn(GitService, "retrieveUserGitRepositories").mockResolvedValue({
@ -594,6 +638,9 @@ describe("MicroagentManagement", () => {
onLoadMore: vi.fn(),
});
// Mock empty search results
mockSearchRepositoriesEmpty();
renderMicroagentManagement();
// Wait for repositories to be loaded
@ -782,6 +829,10 @@ describe("MicroagentManagement", () => {
it("should handle empty search results", async () => {
const user = userEvent.setup();
// Mock empty search results for this test
mockSearchRepositoriesEmpty();
renderMicroagentManagement();
// Wait for repositories to be loaded

View File

@ -9,7 +9,12 @@ import { GitProviderDropdown } from "#/components/features/home/git-provider-dro
import { useMicroagentManagementStore } from "#/state/microagent-management-store";
import { GitRepository } from "#/types/git";
import { Provider } from "#/types/settings";
import { cn } from "#/utils/utils";
import {
cn,
shouldIncludeRepository,
getOpenHandsQuery,
hasOpenHandsSuffix,
} from "#/utils/utils";
import { sanitizeQuery } from "#/utils/sanitize-query";
import { I18nKey } from "#/i18n/declaration";
import { useDebounce } from "#/hooks/use-debounce";
@ -55,6 +60,16 @@ export function MicroagentManagementSidebar({
const { data: searchResults, isLoading: isSearchLoading } =
useSearchRepositories(debouncedSearchQuery, selectedProvider, false, 500); // Increase page size to 500 to to retrieve all search results. This should be optimized in the future.
const {
data: userAndOrgLevelRepositorySearchResults,
isLoading: isUserAndOrgLevelRepositoryLoading,
} = useSearchRepositories(
getOpenHandsQuery(selectedProvider),
selectedProvider,
false,
500,
);
// Auto-select provider if there's only one
useEffect(() => {
if (providers.length > 0 && !selectedProvider) {
@ -100,43 +115,61 @@ export function MicroagentManagementSidebar({
return filterRepositoriesByQuery(allRepositories, debouncedSearchQuery);
}, [repositories, debouncedSearchQuery, searchResults]);
// Process personal and organization repositories from search results
useEffect(() => {
if (!filteredRepositories?.length) {
if (!userAndOrgLevelRepositorySearchResults?.length) {
setPersonalRepositories([]);
setOrganizationRepositories([]);
setRepositories([]);
return;
}
const personalRepos: GitRepository[] = [];
const organizationRepos: GitRepository[] = [];
// Process personal repositories with exact match filtering
if (userAndOrgLevelRepositorySearchResults?.length) {
userAndOrgLevelRepositorySearchResults.forEach((repo: GitRepository) => {
if (
hasOpenHandsSuffix(repo, selectedProvider) &&
shouldIncludeRepository(repo, debouncedSearchQuery)
) {
if (repo.owner_type === "user") {
personalRepos.push(repo);
} else if (repo.owner_type === "organization") {
organizationRepos.push(repo);
}
}
});
}
setPersonalRepositories(personalRepos);
setOrganizationRepositories(organizationRepos);
}, [
userAndOrgLevelRepositorySearchResults,
selectedProvider,
debouncedSearchQuery,
setPersonalRepositories,
setOrganizationRepositories,
]);
// Process other repositories (non-OpenHands repositories) from filteredRepositories
useEffect(() => {
if (!filteredRepositories?.length) {
setRepositories([]);
return;
}
const otherRepos: GitRepository[] = [];
filteredRepositories.forEach((repo: GitRepository) => {
const hasOpenHandsSuffix =
selectedProvider === "gitlab"
? repo.full_name.endsWith("/openhands-config")
: repo.full_name.endsWith("/.openhands");
if (repo.owner_type === "user" && hasOpenHandsSuffix) {
personalRepos.push(repo);
} else if (repo.owner_type === "organization" && hasOpenHandsSuffix) {
organizationRepos.push(repo);
} else {
// Only include repositories that don't have the OpenHands suffix
if (!hasOpenHandsSuffix(repo, selectedProvider)) {
otherRepos.push(repo);
}
});
setPersonalRepositories(personalRepos);
setOrganizationRepositories(organizationRepos);
setRepositories(otherRepos);
}, [
filteredRepositories,
selectedProvider,
setPersonalRepositories,
setOrganizationRepositories,
setRepositories,
]);
}, [filteredRepositories, selectedProvider, setRepositories]);
// Handle scroll to bottom for pagination
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
@ -206,7 +239,7 @@ export function MicroagentManagementSidebar({
</div>
</div>
{isLoading ? (
{isLoading || isUserAndOrgLevelRepositoryLoading ? (
<div className="flex flex-col items-center justify-center gap-4 flex-1">
<Spinner size="sm" />
<span className="text-sm text-white">

View File

@ -3,6 +3,8 @@ import { twMerge } from "tailwind-merge";
import { Provider } from "#/types/settings";
import { SuggestedTaskGroup } from "#/utils/types";
import { ConversationStatus } from "#/types/conversation-status";
import { GitRepository } from "#/types/git";
import { sanitizeQuery } from "#/utils/sanitize-query";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@ -509,3 +511,50 @@ export const getStatusClassName = (status: string) => {
}
return "bg-gray-700 text-gray-300";
};
/**
* Helper function to apply client-side filtering based on search query
* @param repo The Git repository to check
* @param searchQuery The search query string
* @returns True if the repository should be included based on the search query
*/
export const shouldIncludeRepository = (
repo: GitRepository,
searchQuery: string,
): boolean => {
if (!searchQuery.trim()) {
return true;
}
const sanitizedQuery = sanitizeQuery(searchQuery);
const sanitizedRepoName = sanitizeQuery(repo.full_name);
return sanitizedRepoName.includes(sanitizedQuery);
};
/**
* Get the OpenHands query string based on the provider
* @param provider The git provider
* @returns The query string for searching OpenHands repositories
*/
export const getOpenHandsQuery = (provider: Provider | null): string => {
if (provider === "gitlab") {
return "openhands-config";
}
return ".openhands";
};
/**
* Check if a repository has the OpenHands suffix based on the provider
* @param repo The Git repository to check
* @param provider The git provider
* @returns True if the repository has the OpenHands suffix
*/
export const hasOpenHandsSuffix = (
repo: GitRepository,
provider: Provider | null,
): boolean => {
if (provider === "gitlab") {
return repo.full_name.endsWith("/openhands-config");
}
return repo.full_name.endsWith("/.openhands");
};