mirror of
https://github.com/qodo-ai/pr-agent.git
synced 2025-07-03 04:10:49 +08:00
feat: enhance Azure DevOps integration with improved error handling and PR commands
This commit is contained in:
@ -183,6 +183,7 @@ class AzureDevopsProvider(GitProvider):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def set_pr(self, pr_url: str):
|
def set_pr(self, pr_url: str):
|
||||||
|
self.pr_url = pr_url
|
||||||
self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url)
|
self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url)
|
||||||
self.pr = self._get_pr()
|
self.pr = self._get_pr()
|
||||||
|
|
||||||
@ -614,8 +615,11 @@ class AzureDevopsProvider(GitProvider):
|
|||||||
return pr_id
|
return pr_id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if get_settings().config.verbosity_level >= 2:
|
if get_settings().config.verbosity_level >= 2:
|
||||||
get_logger().error(f"Failed to get pr id, error: {e}")
|
get_logger().info(f"Failed to get pr id, error: {e}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def publish_file_comments(self, file_comments: list) -> bool:
|
def publish_file_comments(self, file_comments: list) -> bool:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def get_line_link(self, relevant_file: str, relevant_line_start: int, relevant_line_end: int = None) -> str:
|
||||||
|
return self.pr_url+f"?_a=files&path={relevant_file}"
|
||||||
|
@ -33,20 +33,16 @@ azure_devops_server = get_settings().get("azure_devops_server")
|
|||||||
WEBHOOK_USERNAME = azure_devops_server.get("webhook_username")
|
WEBHOOK_USERNAME = azure_devops_server.get("webhook_username")
|
||||||
WEBHOOK_PASSWORD = azure_devops_server.get("webhook_password")
|
WEBHOOK_PASSWORD = azure_devops_server.get("webhook_password")
|
||||||
|
|
||||||
def handle_request(
|
def handle_request_comment( url: str, body: str, log_context: dict
|
||||||
background_tasks: BackgroundTasks, url: str, body: str, log_context: dict
|
|
||||||
):
|
):
|
||||||
log_context["action"] = body
|
log_context["action"] = body
|
||||||
log_context["api_url"] = url
|
log_context["api_url"] = url
|
||||||
|
|
||||||
async def inner():
|
try:
|
||||||
try:
|
with get_logger().contextualize(**log_context):
|
||||||
with get_logger().contextualize(**log_context):
|
await PRAgent().handle_request(url, body)
|
||||||
await PRAgent().handle_request(url, body)
|
except Exception as e:
|
||||||
except Exception as e:
|
get_logger().exception(f"Failed to handle webhook", artifact={"url": url, "body": body}, error=str(e))
|
||||||
get_logger().error(f"Failed to handle webhook: {e}")
|
|
||||||
|
|
||||||
background_tasks.add_task(inner)
|
|
||||||
|
|
||||||
|
|
||||||
# currently only basic auth is supported with azure webhooks
|
# currently only basic auth is supported with azure webhooks
|
||||||
@ -68,6 +64,9 @@ async def _perform_commands_azure(commands_conf: str, agent: PRAgent, api_url: s
|
|||||||
get_logger().info(f"Auto feedback is disabled, skipping auto commands for PR {api_url=}", **log_context)
|
get_logger().info(f"Auto feedback is disabled, skipping auto commands for PR {api_url=}", **log_context)
|
||||||
return
|
return
|
||||||
commands = get_settings().get(f"azure_devops_server.{commands_conf}")
|
commands = get_settings().get(f"azure_devops_server.{commands_conf}")
|
||||||
|
if not commands:
|
||||||
|
return
|
||||||
|
|
||||||
get_settings().set("config.is_auto_command", True)
|
get_settings().set("config.is_auto_command", True)
|
||||||
for command in commands:
|
for command in commands:
|
||||||
try:
|
try:
|
||||||
@ -83,12 +82,7 @@ async def _perform_commands_azure(commands_conf: str, agent: PRAgent, api_url: s
|
|||||||
get_logger().error(f"Failed to perform command {command}: {e}")
|
get_logger().error(f"Failed to perform command {command}: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", dependencies=[Depends(authorize)])
|
async def handle_request_azure(data, log_context):
|
||||||
async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
|
||||||
log_context = {"server_type": "azure_devops_server"}
|
|
||||||
data = await request.json()
|
|
||||||
get_logger().info(json.dumps(data))
|
|
||||||
|
|
||||||
actions = []
|
actions = []
|
||||||
if data["eventType"] == "git.pullrequest.created":
|
if data["eventType"] == "git.pullrequest.created":
|
||||||
# API V1 (latest)
|
# API V1 (latest)
|
||||||
@ -96,7 +90,10 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
|||||||
log_context["event"] = data["eventType"]
|
log_context["event"] = data["eventType"]
|
||||||
log_context["api_url"] = pr_url
|
log_context["api_url"] = pr_url
|
||||||
await _perform_commands_azure("pr_commands", PRAgent(), pr_url, log_context)
|
await _perform_commands_azure("pr_commands", PRAgent(), pr_url, log_context)
|
||||||
return
|
return JSONResponse(
|
||||||
|
status_code=status.HTTP_202_ACCEPTED,
|
||||||
|
content=jsonable_encoder({"message": "webhook triggered successfully"})
|
||||||
|
)
|
||||||
elif data["eventType"] == "ms.vss-code.git-pullrequest-comment-event" and "content" in data["resource"]["comment"]:
|
elif data["eventType"] == "ms.vss-code.git-pullrequest-comment-event" and "content" in data["resource"]["comment"]:
|
||||||
if available_commands_rgx.match(data["resource"]["comment"]["content"]):
|
if available_commands_rgx.match(data["resource"]["comment"]["content"]):
|
||||||
if(data["resourceVersion"] == "2.0"):
|
if(data["resourceVersion"] == "2.0"):
|
||||||
@ -124,7 +121,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
|||||||
|
|
||||||
for action in actions:
|
for action in actions:
|
||||||
try:
|
try:
|
||||||
handle_request(background_tasks, pr_url, action, log_context)
|
handle_request_comment(pr_url, action, log_context)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
get_logger().error("Azure DevOps Trigger failed. Error:" + str(e))
|
get_logger().error("Azure DevOps Trigger failed. Error:" + str(e))
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -135,6 +132,18 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
|||||||
status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder({"message": "webhook triggered successfully"})
|
status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder({"message": "webhook triggered successfully"})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.post("/", dependencies=[Depends(authorize)])
|
||||||
|
async def handle_webhook(background_tasks: BackgroundTasks, request: Request):
|
||||||
|
log_context = {"server_type": "azure_devops_server"}
|
||||||
|
data = await request.json()
|
||||||
|
get_logger().info(json.dumps(data))
|
||||||
|
|
||||||
|
background_tasks.add_task(handle_request_azure, data, log_context)
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=status.HTTP_202_ACCEPTED, content=jsonable_encoder({"message": "webhook triggered successfully"})
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
@ -326,3 +326,11 @@ utilize_auto_best_practices = true # public - disable usage of auto best practic
|
|||||||
extra_instructions = "" # public - extra instructions to the auto best practices generation prompt
|
extra_instructions = "" # public - extra instructions to the auto best practices generation prompt
|
||||||
content = ""
|
content = ""
|
||||||
max_patterns = 5 # max number of patterns to be detected
|
max_patterns = 5 # max number of patterns to be detected
|
||||||
|
|
||||||
|
|
||||||
|
[azure_devops_server]
|
||||||
|
pr_commands = [
|
||||||
|
"/describe",
|
||||||
|
"/review",
|
||||||
|
"/improve",
|
||||||
|
]
|
@ -683,8 +683,9 @@ class PRDescription:
|
|||||||
filename = filename.strip()
|
filename = filename.strip()
|
||||||
link = self.git_provider.get_line_link(filename, relevant_line_start=-1)
|
link = self.git_provider.get_line_link(filename, relevant_line_start=-1)
|
||||||
if (not link or not diff_plus_minus) and ('additional files' not in filename.lower()):
|
if (not link or not diff_plus_minus) and ('additional files' not in filename.lower()):
|
||||||
get_logger().warning(f"Error getting line link for '{filename}'")
|
# get_logger().warning(f"Error getting line link for '{filename}'")
|
||||||
continue
|
link = ""
|
||||||
|
# continue
|
||||||
|
|
||||||
# Add file data to the PR body
|
# Add file data to the PR body
|
||||||
file_change_description_br = insert_br_after_x_chars(file_change_description, x=(delta - 5))
|
file_change_description_br = insert_br_after_x_chars(file_change_description, x=(delta - 5))
|
||||||
|
Reference in New Issue
Block a user