feat: enhance Azure DevOps integration with improved error handling and PR commands

This commit is contained in:
Hussam.lawen
2025-02-26 16:40:46 +02:00
parent 70f47336d6
commit 0a4c02c8b3
4 changed files with 43 additions and 21 deletions

View File

@ -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}"

View 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"}

View File

@ -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",
]

View File

@ -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))