mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
[Feat]: Add suggested tasks for Gitlab (#8049)
This commit is contained in:
parent
50baf3fd18
commit
e694fc2d58
@ -23,6 +23,7 @@ const MOCK_TASK_1: SuggestedTask = {
|
||||
repo: "repo1",
|
||||
title: "Task 1",
|
||||
task_type: "MERGE_CONFLICTS",
|
||||
git_provider: "github",
|
||||
};
|
||||
|
||||
const MOCK_TASK_2: SuggestedTask = {
|
||||
@ -30,6 +31,7 @@ const MOCK_TASK_2: SuggestedTask = {
|
||||
repo: "repo2",
|
||||
title: "Task 2",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
};
|
||||
|
||||
const MOCK_TASK_3: SuggestedTask = {
|
||||
@ -37,6 +39,7 @@ const MOCK_TASK_3: SuggestedTask = {
|
||||
repo: "repo3",
|
||||
title: "Task 3",
|
||||
task_type: "UNRESOLVED_COMMENTS",
|
||||
git_provider: "gitlab",
|
||||
};
|
||||
|
||||
const MOCK_TASK_4: SuggestedTask = {
|
||||
@ -44,6 +47,7 @@ const MOCK_TASK_4: SuggestedTask = {
|
||||
repo: "repo4",
|
||||
title: "Task 4",
|
||||
task_type: "OPEN_ISSUE",
|
||||
git_provider: "gitlab",
|
||||
};
|
||||
|
||||
const MOCK_RESPOSITORIES: GitRepository[] = [
|
||||
@ -119,7 +123,11 @@ describe("TaskCard", () => {
|
||||
|
||||
expect(createConversationSpy).toHaveBeenCalledWith(
|
||||
MOCK_RESPOSITORIES[0],
|
||||
getMergeConflictPrompt(MOCK_TASK_1.issue_number, MOCK_TASK_1.repo),
|
||||
getMergeConflictPrompt(
|
||||
MOCK_TASK_1.git_provider,
|
||||
MOCK_TASK_1.issue_number,
|
||||
MOCK_TASK_1.repo,
|
||||
),
|
||||
[],
|
||||
undefined,
|
||||
);
|
||||
@ -135,7 +143,11 @@ describe("TaskCard", () => {
|
||||
|
||||
expect(createConversationSpy).toHaveBeenCalledWith(
|
||||
MOCK_RESPOSITORIES[1],
|
||||
getFailingChecksPrompt(MOCK_TASK_2.issue_number, MOCK_TASK_2.repo),
|
||||
getFailingChecksPrompt(
|
||||
MOCK_TASK_2.git_provider,
|
||||
MOCK_TASK_2.issue_number,
|
||||
MOCK_TASK_2.repo,
|
||||
),
|
||||
[],
|
||||
undefined,
|
||||
);
|
||||
@ -151,7 +163,11 @@ describe("TaskCard", () => {
|
||||
|
||||
expect(createConversationSpy).toHaveBeenCalledWith(
|
||||
MOCK_RESPOSITORIES[2],
|
||||
getUnresolvedCommentsPrompt(MOCK_TASK_3.issue_number, MOCK_TASK_3.repo),
|
||||
getUnresolvedCommentsPrompt(
|
||||
MOCK_TASK_3.git_provider,
|
||||
MOCK_TASK_3.issue_number,
|
||||
MOCK_TASK_3.repo,
|
||||
),
|
||||
[],
|
||||
undefined,
|
||||
);
|
||||
@ -167,7 +183,11 @@ describe("TaskCard", () => {
|
||||
|
||||
expect(createConversationSpy).toHaveBeenCalledWith(
|
||||
MOCK_RESPOSITORIES[3],
|
||||
getOpenIssuePrompt(MOCK_TASK_4.issue_number, MOCK_TASK_4.repo),
|
||||
getOpenIssuePrompt(
|
||||
MOCK_TASK_4.git_provider,
|
||||
MOCK_TASK_4.issue_number,
|
||||
MOCK_TASK_4.repo,
|
||||
),
|
||||
[],
|
||||
undefined,
|
||||
);
|
||||
|
||||
@ -11,30 +11,35 @@ const rawTasks: SuggestedTask[] = [
|
||||
repo: "repo1",
|
||||
title: "Task 1",
|
||||
task_type: "MERGE_CONFLICTS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 2,
|
||||
repo: "repo1",
|
||||
title: "Task 2",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 3,
|
||||
repo: "repo2",
|
||||
title: "Task 3",
|
||||
task_type: "UNRESOLVED_COMMENTS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 4,
|
||||
repo: "repo2",
|
||||
title: "Task 4",
|
||||
task_type: "OPEN_ISSUE",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 5,
|
||||
repo: "repo3",
|
||||
title: "Task 5",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
];
|
||||
|
||||
@ -47,12 +52,14 @@ const groupedTasks: SuggestedTaskGroup[] = [
|
||||
repo: "repo1",
|
||||
title: "Task 1",
|
||||
task_type: "MERGE_CONFLICTS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 2,
|
||||
repo: "repo1",
|
||||
title: "Task 2",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -64,12 +71,14 @@ const groupedTasks: SuggestedTaskGroup[] = [
|
||||
repo: "repo2",
|
||||
title: "Task 3",
|
||||
task_type: "UNRESOLVED_COMMENTS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 4,
|
||||
repo: "repo2",
|
||||
title: "Task 4",
|
||||
task_type: "OPEN_ISSUE",
|
||||
git_provider: "github",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -81,6 +90,7 @@ const groupedTasks: SuggestedTaskGroup[] = [
|
||||
repo: "repo3",
|
||||
title: "Task 5",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -1,48 +1,94 @@
|
||||
import { Provider } from "#/types/settings";
|
||||
import { SuggestedTaskType } from "./task.types";
|
||||
|
||||
// Helper function to get provider-specific terminology
|
||||
const getProviderTerms = (git_provider: Provider) => {
|
||||
if (git_provider === "gitlab") {
|
||||
return {
|
||||
requestType: "Merge Request",
|
||||
requestTypeShort: "MR",
|
||||
apiName: "GitLab API",
|
||||
tokenEnvVar: "GITLAB_TOKEN",
|
||||
ciSystem: "CI pipelines",
|
||||
ciProvider: "GitLab",
|
||||
requestVerb: "merge request",
|
||||
};
|
||||
}
|
||||
return {
|
||||
requestType: "Pull Request",
|
||||
requestTypeShort: "PR",
|
||||
apiName: "GitHub API",
|
||||
tokenEnvVar: "GITHUB_TOKEN",
|
||||
ciSystem: "GitHub Actions",
|
||||
ciProvider: "GitHub",
|
||||
requestVerb: "pull request",
|
||||
};
|
||||
};
|
||||
|
||||
export const getMergeConflictPrompt = (
|
||||
git_provider: Provider,
|
||||
issueNumber: number,
|
||||
repo: string,
|
||||
) => `You are working on Pull Request #${issueNumber} in repository ${repo}. You need to fix the merge conflicts.
|
||||
Use the GitHub API to retrieve the PR details. Check out the branch from that pull request and look at the diff versus the base branch of the PR to understand the PR's intention.
|
||||
) => {
|
||||
const terms = getProviderTerms(git_provider);
|
||||
|
||||
return `You are working on ${terms.requestType} #${issueNumber} in repository ${repo}. You need to fix the merge conflicts.
|
||||
Use the ${terms.apiName} with the ${terms.tokenEnvVar} environment variable to retrieve the ${terms.requestTypeShort} details. Check out the branch from that ${terms.requestVerb} and look at the diff versus the base branch of the ${terms.requestTypeShort} to understand the ${terms.requestTypeShort}'s intention.
|
||||
Then resolve the merge conflicts. If you aren't sure what the right solution is, look back through the commit history at the commits that introduced the conflict and resolve them accordingly.`;
|
||||
};
|
||||
|
||||
export const getFailingChecksPrompt = (
|
||||
git_provider: Provider,
|
||||
issueNumber: number,
|
||||
repo: string,
|
||||
) => `You are working on Pull Request #${issueNumber} in repository ${repo}. You need to fix the failing CI checks.
|
||||
Use the GitHub API to retrieve the PR details. Check out the branch from that pull request and look at the diff versus the base branch of the PR to understand the PR's intention.
|
||||
Then use the GitHub API to look at the GitHub Actions that are failing on the most recent commit. Try and reproduce the failure locally.
|
||||
Get things working locally, then push your changes. Sleep for 30 seconds at a time until the GitHub actions have run again. If they are still failing, repeat the process.`;
|
||||
) => {
|
||||
const terms = getProviderTerms(git_provider);
|
||||
|
||||
return `You are working on ${terms.requestType} #${issueNumber} in repository ${repo}. You need to fix the failing CI checks.
|
||||
Use the ${terms.apiName} with the ${terms.tokenEnvVar} environment variable to retrieve the ${terms.requestTypeShort} details. Check out the branch from that ${terms.requestVerb} and look at the diff versus the base branch of the ${terms.requestTypeShort} to understand the ${terms.requestTypeShort}'s intention.
|
||||
Then use the ${terms.apiName} to look at the ${terms.ciSystem} that are failing on the most recent commit. Try and reproduce the failure locally.
|
||||
Get things working locally, then push your changes. Sleep for 30 seconds at a time until the ${terms.ciProvider} ${terms.ciSystem.toLowerCase()} have run again. If they are still failing, repeat the process.`;
|
||||
};
|
||||
|
||||
export const getUnresolvedCommentsPrompt = (
|
||||
git_provider: Provider,
|
||||
issueNumber: number,
|
||||
repo: string,
|
||||
) => `You are working on Pull Request #${issueNumber} in repository ${repo}. You need to resolve the remaining comments from reviewers.
|
||||
Use the GitHub API to retrieve the PR details. Check out the branch from that pull request and look at the diff versus the base branch of the PR to understand the PR's intention.
|
||||
Then use the GitHub API to retrieve all the feedback on the PR so far. If anything hasn't been addressed, address it and commit your changes back to the same branch.`;
|
||||
) => {
|
||||
const terms = getProviderTerms(git_provider);
|
||||
|
||||
return `You are working on ${terms.requestType} #${issueNumber} in repository ${repo}. You need to resolve the remaining comments from reviewers.
|
||||
Use the ${terms.apiName} with the ${terms.tokenEnvVar} environment variable to retrieve the ${terms.requestTypeShort} details. Check out the branch from that ${terms.requestVerb} and look at the diff versus the base branch of the ${terms.requestTypeShort} to understand the ${terms.requestTypeShort}'s intention.
|
||||
Then use the ${terms.apiName} to retrieve all the feedback on the ${terms.requestTypeShort} so far. If anything hasn't been addressed, address it and commit your changes back to the same branch.`;
|
||||
};
|
||||
|
||||
export const getOpenIssuePrompt = (
|
||||
git_provider: Provider,
|
||||
issueNumber: number,
|
||||
repo: string,
|
||||
) => `You are working on Issue #${issueNumber} in repository ${repo}. Your goal is to fix the issue
|
||||
Use the GitHub API to retrieve the issue details and any comments on the issue. Then check out a new branch and investigate what changes will need to be made
|
||||
Finally, make the required changes and open up a pull request. Be sure to reference the issue in the PR description`;
|
||||
) => {
|
||||
const terms = getProviderTerms(git_provider);
|
||||
|
||||
return `You are working on Issue #${issueNumber} in repository ${repo}. Your goal is to fix the issue.
|
||||
Use the ${terms.apiName} with the ${terms.tokenEnvVar} environment variable to retrieve the issue details and any comments on the issue. Then check out a new branch and investigate what changes will need to be made.
|
||||
Finally, make the required changes and open up a ${terms.requestVerb}. Be sure to reference the issue in the ${terms.requestTypeShort} description.`;
|
||||
};
|
||||
|
||||
export const getPromptForQuery = (
|
||||
git_provider: Provider,
|
||||
type: SuggestedTaskType,
|
||||
issueNumber: number,
|
||||
repo: string,
|
||||
) => {
|
||||
switch (type) {
|
||||
case "MERGE_CONFLICTS":
|
||||
return getMergeConflictPrompt(issueNumber, repo);
|
||||
return getMergeConflictPrompt(git_provider, issueNumber, repo);
|
||||
case "FAILING_CHECKS":
|
||||
return getFailingChecksPrompt(issueNumber, repo);
|
||||
return getFailingChecksPrompt(git_provider, issueNumber, repo);
|
||||
case "UNRESOLVED_COMMENTS":
|
||||
return getUnresolvedCommentsPrompt(issueNumber, repo);
|
||||
return getUnresolvedCommentsPrompt(git_provider, issueNumber, repo);
|
||||
case "OPEN_ISSUE":
|
||||
return getOpenIssuePrompt(issueNumber, repo);
|
||||
return getOpenIssuePrompt(git_provider, issueNumber, repo);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { cn } from "#/utils/utils";
|
||||
import { useUserRepositories } from "#/hooks/query/use-user-repositories";
|
||||
import { getPromptForQuery } from "./get-prompt-for-query";
|
||||
import { TaskIssueNumber } from "./task-issue-number";
|
||||
import { Provider } from "#/types/settings";
|
||||
|
||||
const getTaskTypeMap = (
|
||||
t: (key: string) => string,
|
||||
@ -26,18 +27,21 @@ export function TaskCard({ task }: TaskCardProps) {
|
||||
const isCreatingConversation = useIsCreatingConversation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getRepo = (repo: string) => {
|
||||
const getRepo = (repo: string, git_provider: Provider) => {
|
||||
const repositoriesList = repositories?.pages.flatMap((page) => page.data);
|
||||
const selectedRepo = repositoriesList?.find(
|
||||
(repository) => repository.full_name === repo,
|
||||
(repository) =>
|
||||
repository.full_name === repo &&
|
||||
repository.git_provider === git_provider,
|
||||
);
|
||||
|
||||
return selectedRepo;
|
||||
};
|
||||
|
||||
const handleLaunchConversation = () => {
|
||||
const repo = getRepo(task.repo);
|
||||
const repo = getRepo(task.repo, task.git_provider);
|
||||
const query = getPromptForQuery(
|
||||
task.git_provider,
|
||||
task.task_type,
|
||||
task.issue_number,
|
||||
task.repo,
|
||||
@ -49,8 +53,16 @@ export function TaskCard({ task }: TaskCardProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const hrefType = task.task_type === "OPEN_ISSUE" ? "issues" : "pull";
|
||||
const href = `https://github.com/${task.repo}/${hrefType}/${task.issue_number}`;
|
||||
// Determine the correct URL format based on git provider
|
||||
let href: string;
|
||||
if (task.git_provider === "gitlab") {
|
||||
const issueType =
|
||||
task.task_type === "OPEN_ISSUE" ? "issues" : "merge_requests";
|
||||
href = `https://gitlab.com/${task.repo}/-/${issueType}/${task.issue_number}`;
|
||||
} else {
|
||||
const hrefType = task.task_type === "OPEN_ISSUE" ? "issues" : "pull";
|
||||
href = `https://github.com/${task.repo}/${hrefType}/${task.issue_number}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="py-3 border-b border-[#717888] flex items-center pr-6">
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Provider } from "#/types/settings";
|
||||
|
||||
export type SuggestedTaskType =
|
||||
| "MERGE_CONFLICTS"
|
||||
| "FAILING_CHECKS"
|
||||
@ -5,6 +7,7 @@ export type SuggestedTaskType =
|
||||
| "OPEN_ISSUE"; // This is a task type identifier, not a UI string
|
||||
|
||||
export interface SuggestedTask {
|
||||
git_provider: Provider;
|
||||
issue_number: number;
|
||||
repo: string;
|
||||
title: string;
|
||||
|
||||
@ -7,6 +7,7 @@ const TASKS_1: SuggestedTask[] = [
|
||||
title: "Fix merge conflicts",
|
||||
repo: "octocat/hello-world",
|
||||
task_type: "MERGE_CONFLICTS",
|
||||
git_provider: "github",
|
||||
},
|
||||
];
|
||||
|
||||
@ -16,54 +17,63 @@ const TASKS_2: SuggestedTask[] = [
|
||||
title: "Fix broken CI checks",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 281,
|
||||
title: "Fix issue",
|
||||
repo: "octocat/earth",
|
||||
task_type: "UNRESOLVED_COMMENTS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 293,
|
||||
title: "Update documentation",
|
||||
repo: "octocat/earth",
|
||||
task_type: "OPEN_ISSUE",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 305,
|
||||
title: "Refactor user service",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 312,
|
||||
title: "Fix styling bug",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 327,
|
||||
title: "Add unit tests",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 331,
|
||||
title: "Implement dark mode",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 345,
|
||||
title: "Optimize build process",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
{
|
||||
issue_number: 352,
|
||||
title: "Update dependencies",
|
||||
repo: "octocat/earth",
|
||||
task_type: "FAILING_CHECKS",
|
||||
git_provider: "github",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -14,14 +14,16 @@ export function groupSuggestedTasks(
|
||||
const groupsMap: Record<string, SuggestedTaskGroup> = {};
|
||||
|
||||
for (const task of tasks) {
|
||||
if (!groupsMap[task.repo]) {
|
||||
groupsMap[task.repo] = {
|
||||
title: task.repo,
|
||||
const groupKey = `${task.repo}`;
|
||||
|
||||
if (!groupsMap[groupKey]) {
|
||||
groupsMap[groupKey] = {
|
||||
title: groupKey,
|
||||
tasks: [],
|
||||
};
|
||||
}
|
||||
|
||||
groupsMap[task.repo].tasks.push(task);
|
||||
groupsMap[groupKey].tasks.push(task);
|
||||
}
|
||||
|
||||
return Object.values(groupsMap);
|
||||
|
||||
@ -351,6 +351,7 @@ class GitHubService(BaseGitService, GitService):
|
||||
if task_type != TaskType.OPEN_PR:
|
||||
tasks.append(
|
||||
SuggestedTask(
|
||||
git_provider=ProviderType.GITHUB,
|
||||
task_type=task_type,
|
||||
repo=repo_name,
|
||||
issue_number=pr['number'],
|
||||
@ -363,6 +364,7 @@ class GitHubService(BaseGitService, GitService):
|
||||
repo_name = issue['repository']['nameWithOwner']
|
||||
tasks.append(
|
||||
SuggestedTask(
|
||||
git_provider=ProviderType.GITHUB,
|
||||
task_type=TaskType.OPEN_ISSUE,
|
||||
repo=repo_name,
|
||||
issue_number=issue['number'],
|
||||
|
||||
@ -10,6 +10,8 @@ from openhands.integrations.service_types import (
|
||||
ProviderType,
|
||||
Repository,
|
||||
RequestMethod,
|
||||
SuggestedTask,
|
||||
TaskType,
|
||||
UnknownException,
|
||||
User,
|
||||
)
|
||||
@ -106,7 +108,7 @@ class GitLabService(BaseGitService, GitService):
|
||||
except httpx.HTTPError as e:
|
||||
raise self.handle_http_error(e)
|
||||
|
||||
async def execute_graphql_query(self, query: str, variables: dict[str, Any]) -> Any:
|
||||
async def execute_graphql_query(self, query: str, variables: dict[str, Any] = {}) -> Any:
|
||||
"""
|
||||
Execute a GraphQL query against the GitLab GraphQL API
|
||||
|
||||
@ -244,6 +246,138 @@ class GitLabService(BaseGitService, GitService):
|
||||
for repo in all_repos
|
||||
]
|
||||
|
||||
async def get_suggested_tasks(self) -> list[SuggestedTask]:
|
||||
"""Get suggested tasks for the authenticated user across all repositories.
|
||||
|
||||
Returns:
|
||||
- Merge requests authored by the user.
|
||||
- Issues assigned to the user.
|
||||
"""
|
||||
# Get user info to use in queries
|
||||
user = await self.get_user()
|
||||
username = user.login
|
||||
|
||||
# GraphQL query to get merge requests
|
||||
query = """
|
||||
query GetUserTasks {
|
||||
currentUser {
|
||||
authoredMergeRequests(state: opened, sort: UPDATED_DESC, first: 100) {
|
||||
nodes {
|
||||
id
|
||||
iid
|
||||
title
|
||||
project {
|
||||
fullPath
|
||||
}
|
||||
conflicts
|
||||
mergeStatus
|
||||
pipelines(first: 1) {
|
||||
nodes {
|
||||
status
|
||||
}
|
||||
}
|
||||
discussions(first: 100) {
|
||||
nodes {
|
||||
notes {
|
||||
nodes {
|
||||
resolvable
|
||||
resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
try:
|
||||
tasks: list[SuggestedTask] = []
|
||||
|
||||
# Get merge requests using GraphQL
|
||||
response = await self.execute_graphql_query(query)
|
||||
data = response.get('currentUser', {})
|
||||
|
||||
# Process merge requests
|
||||
merge_requests = data.get('authoredMergeRequests', {}).get('nodes', [])
|
||||
for mr in merge_requests:
|
||||
repo_name = mr.get('project', {}).get('fullPath', '')
|
||||
mr_number = mr.get('iid')
|
||||
title = mr.get('title', '')
|
||||
|
||||
# Start with default task type
|
||||
task_type = TaskType.OPEN_PR
|
||||
|
||||
# Check for specific states
|
||||
if mr.get('conflicts'):
|
||||
task_type = TaskType.MERGE_CONFLICTS
|
||||
elif (
|
||||
mr.get('pipelines', {}).get('nodes', [])
|
||||
and mr.get('pipelines', {}).get('nodes', [])[0].get('status')
|
||||
== 'FAILED'
|
||||
):
|
||||
task_type = TaskType.FAILING_CHECKS
|
||||
else:
|
||||
# Check for unresolved comments
|
||||
has_unresolved_comments = False
|
||||
for discussion in mr.get('discussions', {}).get('nodes', []):
|
||||
for note in discussion.get('notes', {}).get('nodes', []):
|
||||
if note.get('resolvable') and not note.get('resolved'):
|
||||
has_unresolved_comments = True
|
||||
break
|
||||
if has_unresolved_comments:
|
||||
break
|
||||
|
||||
if has_unresolved_comments:
|
||||
task_type = TaskType.UNRESOLVED_COMMENTS
|
||||
|
||||
# Only add the task if it's not OPEN_PR
|
||||
if task_type != TaskType.OPEN_PR:
|
||||
tasks.append(
|
||||
SuggestedTask(
|
||||
git_provider=ProviderType.GITLAB,
|
||||
task_type=task_type,
|
||||
repo=repo_name,
|
||||
issue_number=mr_number,
|
||||
title=title,
|
||||
)
|
||||
)
|
||||
|
||||
# Get assigned issues using REST API
|
||||
url = f"{self.BASE_URL}/issues"
|
||||
params = {
|
||||
"assignee_username": username,
|
||||
"state": "opened",
|
||||
"scope": "assigned_to_me"
|
||||
}
|
||||
|
||||
issues_response, _ = await self._make_request(
|
||||
method=RequestMethod.GET,
|
||||
url=url,
|
||||
params=params
|
||||
)
|
||||
|
||||
# Process issues
|
||||
for issue in issues_response:
|
||||
repo_name = issue.get('references', {}).get('full', '').split('#')[0].strip()
|
||||
issue_number = issue.get('iid')
|
||||
title = issue.get('title', '')
|
||||
|
||||
tasks.append(
|
||||
SuggestedTask(
|
||||
git_provider=ProviderType.GITLAB,
|
||||
task_type=TaskType.OPEN_ISSUE,
|
||||
repo=repo_name,
|
||||
issue_number=issue_number,
|
||||
title=title,
|
||||
)
|
||||
)
|
||||
|
||||
return tasks
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
gitlab_service_cls = os.environ.get(
|
||||
'OPENHANDS_GITLAB_SERVICE_CLS',
|
||||
|
||||
@ -25,6 +25,7 @@ from openhands.integrations.service_types import (
|
||||
GitService,
|
||||
ProviderType,
|
||||
Repository,
|
||||
SuggestedTask,
|
||||
User,
|
||||
)
|
||||
from openhands.server.types import AppMode
|
||||
@ -227,7 +228,7 @@ class ProviderHandler:
|
||||
|
||||
async def get_repositories(self, sort: str, app_mode: AppMode) -> list[Repository]:
|
||||
"""
|
||||
Get repositories from a selected providers with pagination support
|
||||
Get repositories from providers
|
||||
"""
|
||||
|
||||
all_repos: list[Repository] = []
|
||||
@ -241,6 +242,21 @@ class ProviderHandler:
|
||||
|
||||
return all_repos
|
||||
|
||||
async def get_suggested_tasks(self) -> list[SuggestedTask]:
|
||||
"""
|
||||
Get suggested tasks from providers
|
||||
"""
|
||||
tasks: list[SuggestedTask] = []
|
||||
for provider in self.provider_tokens:
|
||||
try:
|
||||
service = self._get_service(provider)
|
||||
service_repos = await service.get_suggested_tasks()
|
||||
tasks.extend(service_repos)
|
||||
except Exception as e:
|
||||
logger.warning(f'Error fetching repos from {provider}: {e}')
|
||||
|
||||
return tasks
|
||||
|
||||
async def search_repositories(
|
||||
self,
|
||||
query: str,
|
||||
|
||||
@ -23,6 +23,7 @@ class TaskType(str, Enum):
|
||||
|
||||
|
||||
class SuggestedTask(BaseModel):
|
||||
git_provider: ProviderType
|
||||
task_type: TaskType
|
||||
repo: str
|
||||
issue_number: int
|
||||
@ -149,3 +150,7 @@ class GitService(Protocol):
|
||||
async def get_repositories(self, sort: str, app_mode: AppMode) -> list[Repository]:
|
||||
"""Get repositories for the authenticated user"""
|
||||
...
|
||||
|
||||
async def get_suggested_tasks(self) -> list[SuggestedTask]:
|
||||
"""Get suggested tasks for the authenticated user across all repositories"""
|
||||
...
|
||||
|
||||
@ -139,12 +139,9 @@ async def get_suggested_tasks(
|
||||
- PRs owned by the user
|
||||
- Issues assigned to the user.
|
||||
"""
|
||||
|
||||
if provider_tokens and ProviderType.GITHUB in provider_tokens:
|
||||
token = provider_tokens[ProviderType.GITHUB]
|
||||
|
||||
client = GithubServiceImpl(
|
||||
user_id=token.user_id, external_auth_token=access_token, token=token.token
|
||||
if provider_tokens:
|
||||
client = ProviderHandler(
|
||||
provider_tokens=provider_tokens, external_auth_token=access_token
|
||||
)
|
||||
try:
|
||||
tasks: list[SuggestedTask] = await client.get_suggested_tasks()
|
||||
@ -153,16 +150,16 @@ async def get_suggested_tasks(
|
||||
except AuthenticationError as e:
|
||||
return JSONResponse(
|
||||
content=str(e),
|
||||
status_code=401,
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
|
||||
except UnknownException as e:
|
||||
return JSONResponse(
|
||||
content=str(e),
|
||||
status_code=500,
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content='GitHub token required.',
|
||||
content='No providers set.',
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user