""" Issue service - business logic for issue operations. """ from typing import List, Dict, Any, Optional from datetime import datetime from tddai import IssueFetcher, TddaiError from tddai.issue_creator import IssueCreator from gitea.models import Issue, Priority class IssueService: """Service for issue operations.""" def __init__(self): self.issue_fetcher = IssueFetcher() self.issue_creator = IssueCreator() def get_issue(self, issue_number: int) -> Issue: """Get a specific issue by number.""" return self.issue_fetcher.fetch_issue(issue_number) def list_issues(self, state: str = "all") -> List[Issue]: """List issues with optional state filter.""" return self.issue_fetcher.fetch_issues(state) def list_open_issues(self) -> List[Issue]: """List only open issues.""" return self.issue_fetcher.fetch_open_issues() def create_issue(self, title: str, body: str, **kwargs) -> Dict[str, Any]: """Create a new issue.""" return self.issue_creator.create_issue(title, body, **kwargs) def create_enhancement_issue(self, title: str, use_case: str, technical_requirements: str = "", acceptance_criteria: Optional[List[str]] = None, dependencies: Optional[List[str]] = None, priority: str = "Medium") -> Dict[str, Any]: """Create a structured enhancement issue.""" return self.issue_creator.create_enhancement_issue( title, use_case, technical_requirements, acceptance_criteria, dependencies, priority ) def create_from_template(self, template_file: str, **kwargs) -> Dict[str, Any]: """Create issue from template file.""" return self.issue_creator.create_from_template(template_file, **kwargs) def close_issue(self, issue_number: int, comment: str = "") -> Dict[str, Any]: """Close an issue with optional comment.""" from gitea import GiteaClient, GiteaConfig from tddai.config import get_config import os # Get config and create Gitea client config = get_config() gitea_config = GiteaConfig.from_tddai_config(config) auth_token = os.getenv('GITEA_API_TOKEN') if auth_token: gitea_config.auth_token = auth_token gitea_client = GiteaClient(gitea_config) # Close the issue issue = gitea_client.issues.close(issue_number) # If comment provided, add it (this would need API support for comments) # For now, we'll just close the issue # Convert to dict format for consistency return gitea_client.issues.to_dict(issue) def get_issue_details(self, issue_number: int) -> Dict[str, Any]: """Get comprehensive issue details for display purposes.""" issue = self.get_issue(issue_number) # Get additional project management information from tddai.project_manager import ProjectManager project_mgr = ProjectManager() # Get detailed issue data via API for milestone and project information from tddai.config import get_config config = get_config() issue_url = f"{config.issues_api_url}/{issue_number}" detailed_issue = project_mgr._make_api_call('GET', issue_url) # Process labels labels = detailed_issue.get('labels', []) state_labels = [l['name'] for l in labels if l['name'].startswith('status:')] priority_labels = [l['name'] for l in labels if l['name'].startswith('priority:')] type_labels = [l['name'] for l in labels if l['name'].startswith('type:')] other_labels = [l['name'] for l in labels if not any(l['name'].startswith(p) for p in ['status:', 'priority:', 'type:'])] # Determine project column/state if detailed_issue.get('state') == 'closed': if any(l['name'] == 'status:done' for l in labels): column = "Done" else: column = "Closed" else: state_labels = [l['name'] for l in labels if l['name'].startswith('status:')] if state_labels: state = state_labels[0].replace('status:', '') column_map = { 'todo': 'Todo', 'active': 'Active', 'review': 'Review', 'blocked': 'Blocked' } column = column_map.get(state, 'Todo') else: column = "Todo" return { 'number': issue.number, 'title': issue.title, 'body': issue.body, 'state': issue.state, 'created_at': issue.created_at, 'updated_at': issue.updated_at, 'html_url': issue.html_url, 'assignee': issue.assignee.login if issue.assignee else None, 'milestone': detailed_issue.get('milestone'), 'state_label': state_labels[0].replace('status:', '').title() if state_labels else "No state label", 'priority_label': priority_labels[0].replace('priority:', '').title() if priority_labels else "No priority set", 'type_labels': [l.replace('type:', '').title() for l in type_labels], 'other_labels': other_labels, 'kanban_column': column } def get_issue_summary(self, issue_number: int) -> Dict[str, Any]: """Get issue summary for display purposes.""" issue = self.get_issue(issue_number) return { 'number': issue.number, 'title': issue.title, 'body': issue.body, 'state': issue.state, 'priority': issue.priority, 'status': issue.status, 'created_at': issue.created_at, 'updated_at': issue.updated_at, 'html_url': issue.html_url, 'assignee': issue.assignee.login if issue.assignee else None, 'labels': [label.name for label in issue.labels], 'has_milestone': issue.milestone is not None, 'milestone_title': issue.milestone.title if issue.milestone else None } def analyze_coverage(self, issue_number: int) -> Dict[str, Any]: """Analyze test coverage for a specific issue. Args: issue_number: The issue number to analyze Returns: Coverage analysis data for the issue Raises: IssueError: When coverage analysis fails """ from tddai.coverage_analyzer import CoverageAnalyzer analyzer = CoverageAnalyzer() assessment = analyzer.analyze_issue_coverage(issue_number) return { 'issue_number': assessment.issue_number, 'issue_title': assessment.issue_title, 'coverage_percentage': assessment.coverage_percentage, 'requirements': assessment.requirements, 'existing_tests': assessment.existing_tests, 'coverage_gaps': assessment.coverage_gaps, 'recommendations': assessment.recommendations }