| """ |
| Bug Report API for Archon Beta |
| |
| Handles bug report submission to GitHub Issues with automatic context formatting. |
| """ |
|
|
| import os |
| from typing import Any |
|
|
| import httpx |
| from fastapi import APIRouter, HTTPException |
| from pydantic import BaseModel |
|
|
| from ..config.logfire_config import get_logger |
|
|
| logger = get_logger(__name__) |
|
|
| router = APIRouter(prefix="/api/bug-report", tags=["bug-report"]) |
|
|
|
|
| class BugContext(BaseModel): |
| error: dict[str, Any] |
| app: dict[str, Any] |
| system: dict[str, Any] |
| services: dict[str, bool] |
| logs: list[str] |
|
|
|
|
| class BugReportRequest(BaseModel): |
| title: str |
| description: str |
| stepsToReproduce: str |
| expectedBehavior: str |
| actualBehavior: str |
| severity: str |
| component: str |
| context: BugContext |
|
|
|
|
| class BugReportResponse(BaseModel): |
| success: bool |
| issue_number: int | None = None |
| issue_url: str | None = None |
| message: str |
|
|
|
|
| class GitHubService: |
| def __init__(self): |
| self.token = os.getenv("GITHUB_TOKEN") |
| self.repo = os.getenv("GITHUB_REPO", "dynamous-community/Archon-V2-Alpha") |
|
|
| async def create_issue(self, bug_report: BugReportRequest) -> dict[str, Any]: |
| """Create a GitHub issue from a bug report.""" |
|
|
| if not self.token: |
| raise HTTPException(status_code=500, detail="GitHub integration not configured - GITHUB_TOKEN not found") |
|
|
| |
| issue_body = self._format_issue_body(bug_report) |
|
|
| issue_data = { |
| "title": bug_report.title, |
| "body": issue_body, |
| "labels": [ |
| "bug", |
| "auto-report", |
| f"severity:{bug_report.severity}", |
| f"component:{bug_report.component}", |
| ], |
| } |
|
|
| try: |
| async with httpx.AsyncClient(timeout=30.0) as client: |
| response = await client.post( |
| f"https://api.github.com/repos/{self.repo}/issues", |
| headers={ |
| "Authorization": f"Bearer {self.token}", |
| "Accept": "application/vnd.github.v3+json", |
| "User-Agent": "Archon-Bug-Reporter/1.0", |
| }, |
| json=issue_data, |
| ) |
|
|
| if response.status_code == 201: |
| issue_data = response.json() |
| return { |
| "success": True, |
| "issue_number": issue_data["number"], |
| "issue_url": issue_data["html_url"], |
| } |
| elif response.status_code == 401: |
| logger.error("GitHub API authentication failed") |
| raise HTTPException( |
| status_code=500, |
| detail="GitHub authentication failed - check GITHUB_TOKEN permissions", |
| ) |
| else: |
| logger.error(f"GitHub API error: {response.status_code} - {response.text}") |
| raise HTTPException(status_code=500, detail=f"GitHub API error: {response.status_code}") |
|
|
| except httpx.TimeoutException as e: |
| logger.error("GitHub API request timed out") |
| raise HTTPException(status_code=500, detail="GitHub API request timed out") from e |
| except Exception as e: |
| logger.error(f"Unexpected error creating GitHub issue: {e}") |
| raise HTTPException(status_code=500, detail=f"Failed to create GitHub issue: {str(e)}") from e |
|
|
| def _format_issue_body(self, bug_report: BugReportRequest) -> str: |
| """Format the bug report as a GitHub issue body.""" |
|
|
| |
| severity_map = {"low": "π’", "medium": "π‘", "high": "π ", "critical": "π΄"} |
|
|
| |
| component_map = { |
| "knowledge-base": "π", |
| "mcp-integration": "π", |
| "projects-tasks": "π", |
| "settings": "βοΈ", |
| "ui": "π₯οΈ", |
| "infrastructure": "π³", |
| "not-sure": "β", |
| } |
|
|
| severity_emoji = severity_map.get(bug_report.severity, "β") |
| component_emoji = component_map.get(bug_report.component, "β") |
|
|
| return f"""## {severity_emoji} Bug Report |
| |
| **Reported by:** User (Archon Beta) |
| **Severity:** {severity_emoji} {bug_report.severity.title()} |
| **Component:** {component_emoji} {bug_report.component.replace("-", " ").title()} |
| **Version:** {bug_report.context.app.get("version", "unknown")} |
| **Platform:** {bug_report.context.system.get("platform", "unknown")} |
| |
| ### Description |
| {bug_report.description} |
| |
| ### Steps to Reproduce |
| {bug_report.stepsToReproduce or "Not specified"} |
| |
| ### Expected Behavior |
| {bug_report.expectedBehavior or "Not specified"} |
| |
| ### Actual Behavior |
| {bug_report.actualBehavior or "Not specified"} |
| |
| --- |
| |
| ## π§ Technical Context |
| |
| ### Error Details |
| ``` |
| Error: {bug_report.context.error.get("name", "Unknown")} |
| Message: {bug_report.context.error.get("message", "No message")} |
| |
| Stack Trace: |
| {bug_report.context.error.get("stack", "No stack trace available")} |
| ``` |
| |
| ### System Information |
| - **Platform:** {bug_report.context.system.get("platform", "unknown")} |
| - **Version:** {bug_report.context.app.get("version", "unknown")} |
| - **URL:** {bug_report.context.app.get("url", "unknown")} |
| - **Timestamp:** {bug_report.context.app.get("timestamp", "unknown")} |
| - **Memory:** {bug_report.context.system.get("memory", "unknown")} |
| |
| ### Service Status |
| - **Server:** {"β
" if bug_report.context.services.get("server") else "β"} |
| - **MCP:** {"β
" if bug_report.context.services.get("mcp") else "β"} |
| - **Agents:** {"β
" if bug_report.context.services.get("agents") else "β"} |
| |
| ### Recent Logs |
| ``` |
| {chr(10).join(bug_report.context.logs[-10:]) if bug_report.context.logs else "No logs available"} |
| ``` |
| |
| --- |
| *Auto-generated by Archon Bug Reporter* |
| """ |
|
|
|
|
| |
| github_service = GitHubService() |
|
|
|
|
| @router.post("/github", response_model=BugReportResponse) |
| async def create_github_issue(bug_report: BugReportRequest): |
| """ |
| Create a GitHub issue from a bug report. |
| |
| For open source: If no GitHub token is configured, returns a pre-filled |
| GitHub issue creation URL for the user to submit manually. |
| |
| For maintainers: If GitHub token exists, creates the issue directly via API. |
| """ |
|
|
| logger.info( |
| f"Processing bug report: {bug_report.title} (severity: {bug_report.severity}, component: {bug_report.component})" |
| ) |
|
|
| |
| if github_service.token: |
| try: |
| result = await github_service.create_issue(bug_report) |
|
|
| logger.info(f"Successfully created GitHub issue #{result['issue_number']}: {result['issue_url']}") |
|
|
| return BugReportResponse( |
| success=True, |
| issue_number=result["issue_number"], |
| issue_url=result["issue_url"], |
| message=f"Bug report created as issue #{result['issue_number']}", |
| ) |
|
|
| except HTTPException: |
| |
| logger.warning("GitHub API failed, falling back to manual submission") |
| return _create_manual_submission_response(bug_report) |
| except Exception as e: |
| logger.error(f"GitHub API error: {e}, falling back to manual submission") |
| return _create_manual_submission_response(bug_report) |
|
|
| |
| else: |
| logger.info("No GitHub token configured, creating manual submission URL") |
| return _create_manual_submission_response(bug_report) |
|
|
|
|
| def _create_manual_submission_response(bug_report: BugReportRequest) -> BugReportResponse: |
| """Create a response with pre-filled GitHub issue URL for manual submission.""" |
|
|
| |
| issue_body = github_service._format_issue_body(bug_report) |
|
|
| |
| import urllib.parse |
|
|
| base_url = f"https://github.com/{github_service.repo}/issues/new" |
| params = { |
| "template": "bug_report.yml", |
| "title": bug_report.title, |
| "labels": f"bug,auto-report,severity:{bug_report.severity},component:{bug_report.component}", |
| } |
|
|
| |
| params["body"] = issue_body |
|
|
| |
| query_string = urllib.parse.urlencode(params) |
| github_url = f"{base_url}?{query_string}" |
|
|
| return BugReportResponse( |
| success=True, |
| issue_number=None, |
| issue_url=github_url, |
| message="Click the provided URL to submit your bug report to GitHub", |
| ) |
|
|
|
|
| @router.get("/health") |
| async def bug_report_health(): |
| """Health check for bug reporting service.""" |
|
|
| github_configured = bool(os.getenv("GITHUB_TOKEN")) |
| repo_configured = bool(os.getenv("GITHUB_REPO")) |
|
|
| return { |
| "status": "healthy" if github_configured else "degraded", |
| "github_token_configured": github_configured, |
| "github_repo_configured": repo_configured, |
| "repo": os.getenv("GITHUB_REPO", "dynamous-community/Archon-V2-Alpha"), |
| "message": "Bug reporting is ready" if github_configured else "GitHub token not configured", |
| } |
|
|