mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
fix(frontend): org level microagents not appearing (microagent management) (#11218)
This commit is contained in:
parent
d3395172f8
commit
c932cd0815
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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");
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user