Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance Azure DevOps integration with improved error handling a… #1583

Merged
merged 4 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pr_agent/algo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def convert_to_markdown_v2(output_data: dict,
if gfm_supported:
if reference_link is not None and len(reference_link) > 0:
if relevant_lines_str:
issue_str = f"<details><summary><a href='{reference_link}'><strong>{issue_header}</strong></a>\n\n{issue_content}</summary>\n\n{relevant_lines_str}\n\n</details>"
issue_str = f"<details><summary><a href='{reference_link}'><strong>{issue_header}</strong></a>\n\n{issue_content}\n</summary>\n\n{relevant_lines_str}\n\n</details>"
else:
issue_str = f"<a href='{reference_link}'><strong>{issue_header}</strong></a><br>{issue_content}"
else:
Expand Down
6 changes: 5 additions & 1 deletion pr_agent/git_providers/azuredevops_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def is_supported(self, capability: str) -> bool:
return True

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.pr = self._get_pr()

Expand Down Expand Up @@ -614,8 +615,11 @@ def get_pr_id(self):
return pr_id
except Exception as e:
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 ""

def publish_file_comments(self, file_comments: list) -> bool:
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}"
45 changes: 27 additions & 18 deletions pr_agent/servers/azuredevops_server_webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,16 @@
WEBHOOK_USERNAME = azure_devops_server.get("webhook_username")
WEBHOOK_PASSWORD = azure_devops_server.get("webhook_password")

def handle_request(
background_tasks: BackgroundTasks, url: str, body: str, log_context: dict
async def handle_request_comment( url: str, body: str, log_context: dict
):
log_context["action"] = body
log_context["api_url"] = url

async def inner():
try:
with get_logger().contextualize(**log_context):
await PRAgent().handle_request(url, body)
except Exception as e:
get_logger().error(f"Failed to handle webhook: {e}")

background_tasks.add_task(inner)
try:
with get_logger().contextualize(**log_context):
await PRAgent().handle_request(url, body)
except Exception as e:
get_logger().exception(f"Failed to handle webhook", artifact={"url": url, "body": body}, error=str(e))


# currently only basic auth is supported with azure webhooks
Expand All @@ -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)
return
commands = get_settings().get(f"azure_devops_server.{commands_conf}")
if not commands:
return

get_settings().set("config.is_auto_command", True)
for command in commands:
try:
Expand All @@ -83,20 +82,18 @@ async def _perform_commands_azure(commands_conf: str, agent: PRAgent, api_url: s
get_logger().error(f"Failed to perform command {command}: {e}")


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

async def handle_request_azure(data, log_context):
actions = []
if data["eventType"] == "git.pullrequest.created":
# API V1 (latest)
pr_url = unquote(data["resource"]["_links"]["web"]["href"].replace("_apis/git/repositories", "_git"))
log_context["event"] = data["eventType"]
log_context["api_url"] = pr_url
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"]:
if available_commands_rgx.match(data["resource"]["comment"]["content"]):
if(data["resourceVersion"] == "2.0"):
Expand Down Expand Up @@ -124,7 +121,7 @@ async def handle_webhook(background_tasks: BackgroundTasks, request: Request):

for action in actions:
try:
handle_request(background_tasks, pr_url, action, log_context)
await handle_request_comment(pr_url, action, log_context)
except Exception as e:
get_logger().error("Azure DevOps Trigger failed. Error:" + str(e))
return JSONResponse(
Expand All @@ -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"})
)

@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("/")
async def root():
return {"status": "ok"}
Expand Down
8 changes: 8 additions & 0 deletions pr_agent/settings/configuration.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
content = ""
max_patterns = 5 # max number of patterns to be detected


[azure_devops_server]
pr_commands = [
"/describe",
"/review",
"/improve",
]
5 changes: 3 additions & 2 deletions pr_agent/tools/pr_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,8 +683,9 @@ def process_pr_files_prediction(self, pr_body, value):
filename = filename.strip()
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()):
get_logger().warning(f"Error getting line link for '{filename}'")
continue
# get_logger().warning(f"Error getting line link for '{filename}'")
link = ""
# continue

# Add file data to the PR body
file_change_description_br = insert_br_after_x_chars(file_change_description, x=(delta - 5))
Expand Down
Loading