Coverage for src/commands/bug.py: 95%
62 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-05 22:56 -0700
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-05 22:56 -0700
1"""
2Command handler for submitting bug reports.
4This module contains the handler for submitting bug reports to Amperity,
5including current configuration (without tokens) and session logs.
6"""
8import logging
9import os
10import platform
11from datetime import datetime
12from typing import Optional, Any, Dict
14from src.clients.databricks import DatabricksAPIClient
15from src.clients.amperity import AmperityAPIClient
16from src.command_registry import CommandDefinition
17from src.commands.base import CommandResult
18from src.config import get_config_manager, get_amperity_token
19from src.logger import get_current_log_file
22def handle_command(
23 client: Optional[DatabricksAPIClient], **kwargs: Any
24) -> CommandResult:
25 """
26 Submit a bug report to Amperity's API.
28 Args:
29 client: Not used for this command
30 **kwargs: Command parameters including:
31 - description: Bug description from user
32 - rest: Alternative way to provide description
33 - raw_args: Fallback for unparsed args
35 Returns:
36 CommandResult indicating success or failure
37 """
38 # Try to get description from multiple sources
39 description = kwargs.get("description", "").strip()
41 # If no explicit description, check for rest/raw_args (free-form text)
42 if not description:
43 description = kwargs.get("rest", "").strip()
45 if not description and "raw_args" in kwargs:
46 raw_args = kwargs["raw_args"]
47 if isinstance(raw_args, list):
48 description = " ".join(raw_args).strip()
49 elif isinstance(raw_args, str):
50 description = raw_args.strip()
52 if not description:
53 return CommandResult(
54 False,
55 message="Bug description is required. Usage: /bug Your bug description here",
56 )
58 # Check for Amperity token
59 amperity_token = get_amperity_token()
60 if not amperity_token:
61 return CommandResult(
62 False,
63 message="Amperity authentication required. Please run /auth to authenticate first.",
64 )
66 try:
67 # Prepare bug report payload
68 payload = _prepare_bug_report(description)
70 # Submit to Amperity API using the client
71 amperity_client = AmperityAPIClient()
72 success, message = amperity_client.submit_bug_report(payload, amperity_token)
74 if success:
75 logging.debug("Bug report submitted successfully")
76 return CommandResult(
77 True,
78 message="Bug report submitted successfully. Thank you for your feedback!",
79 )
80 else:
81 return CommandResult(False, message=message)
83 except Exception as e:
84 logging.error(f"Error submitting bug report: {e}", exc_info=True)
85 return CommandResult(
86 False, error=e, message=f"Error submitting bug report: {str(e)}"
87 )
90def _prepare_bug_report(description: str) -> Dict[str, Any]:
91 """
92 Prepare the bug report payload.
94 Args:
95 description: User's bug description
97 Returns:
98 Dictionary containing bug report data
99 """
100 # Get config without tokens
101 config_data = _get_sanitized_config()
103 # Get session log content
104 log_content = _get_session_log()
106 # Get system information
107 system_info = {
108 "platform": platform.platform(),
109 "python_version": platform.python_version(),
110 "system": platform.system(),
111 "machine": platform.machine(),
112 }
114 return {
115 "type": "bug_report",
116 "timestamp": datetime.utcnow().isoformat(),
117 "description": description,
118 "config": config_data,
119 "session_log": log_content,
120 "system_info": system_info,
121 }
124def _get_sanitized_config() -> Dict[str, Any]:
125 """
126 Get current configuration without sensitive data (tokens).
128 Returns:
129 Dictionary of sanitized config data
130 """
131 config_manager = get_config_manager()
132 config = config_manager.get_config()
134 # Create sanitized version - NEVER include tokens
135 sanitized = {
136 "workspace_url": config.workspace_url,
137 "active_model": config.active_model,
138 "warehouse_id": config.warehouse_id,
139 "active_catalog": config.active_catalog,
140 "active_schema": config.active_schema,
141 "usage_tracking_consent": config.usage_tracking_consent,
142 }
144 # Remove None values
145 return {k: v for k, v in sanitized.items() if v is not None}
148def _get_session_log() -> str:
149 """
150 Get the current session's log content.
152 Returns:
153 String containing log content or error message
154 """
155 log_file = get_current_log_file()
156 if not log_file or not os.path.exists(log_file):
157 return "Session log not available"
159 try:
160 with open(log_file, "r") as f:
161 # Read last 10KB of log to avoid huge payloads
162 f.seek(0, os.SEEK_END)
163 file_size = f.tell()
164 read_size = min(file_size, 10240) # 10KB max
165 f.seek(max(0, file_size - read_size))
166 return f.read()
167 except Exception as e:
168 logging.error(f"Failed to read session log: {e}")
169 return f"Error reading session log: {str(e)}"
172DEFINITION = CommandDefinition(
173 name="bug",
174 description="Submit a bug report with current configuration and session logs",
175 handler=handle_command,
176 parameters={
177 "description": {
178 "type": "string",
179 "description": "Description of the bug or issue you're experiencing",
180 },
181 "rest": {
182 "type": "string",
183 "description": "Bug description provided as free-form text after the command",
184 },
185 "raw_args": {
186 "type": ["array", "string"],
187 "description": "Raw unparsed arguments for the bug description",
188 },
189 },
190 required_params=[], # No required params since we accept multiple input methods
191 tui_aliases=["/bug"],
192 needs_api_client=False, # We use Amperity token directly
193 visible_to_user=True,
194 visible_to_agent=False, # Bug reports should come from users, not agents
195 usage_hint="Example: /bug The table list is not refreshing properly",
196)