markitect-main/issue_tracker/cli/presenters/views.py

325 lines
14 KiB
Python
Raw Permalink Normal View History

"""
View models for displaying complex data structures.
"""
from typing import Dict, Any, List
from .formatters import OutputFormatter
class WorkspaceView:
"""View for workspace information display."""
@staticmethod
def show_status(summary: Dict[str, Any]) -> None:
"""Display workspace status."""
if summary['clean']:
OutputFormatter.info("No active issue workspace")
print(" Use 'make tdd-start NUM=X' to begin working on an issue")
return
if summary['dirty']:
OutputFormatter.warning("Workspace directory exists but no current issue file")
print(" Run 'make tdd-finish' to clean up or 'make tdd-start' to create new workspace")
return
if summary['active']:
WorkspaceView._show_active_workspace(summary)
else:
OutputFormatter.error("Failed to load workspace")
@staticmethod
def _show_active_workspace(summary: Dict[str, Any]) -> None:
"""Display active workspace details."""
OutputFormatter.header("Active Issue Workspace", "=")
issue_num = summary['issue_number']
issue_title = summary['issue_title']
print(f"🎯 Issue #{issue_num}: {issue_title}")
OutputFormatter.key_value("Status", summary['issue_state'])
OutputFormatter.key_value("Workspace", f"{summary['workspace_dir']}/issue_{issue_num}/")
OutputFormatter.empty_line()
# Test files
test_count = summary['test_count']
test_files = summary.get('test_files', [])
print(f"🧪 Generated Tests ({test_count}):")
if test_files:
for test_file in test_files:
print(f" - {test_file}")
else:
print(" - No tests generated yet")
OutputFormatter.empty_line()
# Workspace files
print("📋 Workspace Files:")
print(" - requirements.md (review and break down issue)")
print(" - test_plan.md (plan test scenarios)")
print(" - tests/ (generated test files)")
OutputFormatter.empty_line()
# Commands
commands = [
"make tdd-add-test (generate another test)",
"make tdd-finish (complete and move tests to main)"
]
OutputFormatter.format_command_list(commands)
@staticmethod
def show_start_success(summary: Dict[str, Any]) -> None:
"""Display successful workspace start."""
issue_num = summary['issue_number']
OutputFormatter.success(f"Workspace created for issue #{issue_num}")
OutputFormatter.key_value("Workspace", f"{summary['workspace_dir']}/issue_{issue_num}/")
OutputFormatter.key_value("Requirements", summary['requirements_file'])
OutputFormatter.key_value("Test plan", summary['test_plan_file'])
OutputFormatter.empty_line()
print("💡 Next steps:")
print(" 1. Review requirements.md and break down the issue")
print(" 2. Plan test scenarios in test_plan.md")
print(" 3. Use 'make tdd-add-test' to generate tests")
print(" 4. Use 'make tdd-finish' when complete")
@staticmethod
def show_finish_success(issue_number: int, test_count: int) -> None:
"""Display successful workspace finish."""
OutputFormatter.info(f"Finishing work on issue #{issue_number}")
OutputFormatter.empty_line()
if test_count > 0:
print(f"📦 Moving {test_count} test(s) to tests/ directory...")
OutputFormatter.success("Tests moved to main tests/ directory")
else:
OutputFormatter.warning("No tests found in workspace")
print("🧹 Cleaning up workspace...")
OutputFormatter.success(f"Issue #{issue_number} workspace cleaned up")
OutputFormatter.empty_line()
print("💡 Next steps:")
print(" - Run 'make test' to verify tests fail (red state)")
print(" - Implement code to make tests pass (green state)")
print(" - Start next issue with 'make tdd-start NUM=X'")
class IssueView:
"""View for issue information display."""
@staticmethod
def show_list(issues: List[Any], title: str = "Project Issues") -> None:
"""Display issue list."""
OutputFormatter.header(title, "=")
if not issues:
print("No issues found")
return
for issue in issues:
status_icon = "🟢" if issue.state == "open" else "🔴"
print(f"{status_icon} #{issue.number}: {issue.title}")
print(f" Status: {issue.state.upper()} | Created: {issue.created_at.strftime('%Y-%m-%d')}")
# Truncate body for list view
body = getattr(issue, 'body', '')
body_preview = body[:80] + "..." if len(body) > 80 else body
if body_preview:
print(f" {body_preview}")
OutputFormatter.empty_line()
print("💡 Tip: Use 'make show-issue NUM=X' for full details")
@staticmethod
def show_open_issues(issues: List[Any]) -> None:
"""Display open issues list."""
OutputFormatter.header("Open Project Issues (Active Backlog)", "=")
if not issues:
print("No open issues found")
return
for issue in issues:
print(f"[OPEN] #{issue.number}: {issue.title}")
created = issue.created_at.strftime('%Y-%m-%d')
updated = issue.updated_at.strftime('%Y-%m-%d')
print(f" Created: {created} | Updated: {updated}")
# Truncate body for list view
body = getattr(issue, 'body', '')
body_preview = body[:80] + "..." if len(body) > 80 else body
if body_preview:
print(f" {body_preview}")
OutputFormatter.empty_line()
print("💡 Tip: Use 'make show-issue NUM=X' for full details or 'make list-issues' for all issues")
@staticmethod
def show_creation_success(result: Dict[str, Any], issue_type: str = "issue") -> None:
"""Display successful issue creation."""
OutputFormatter.success(f"{issue_type.title()} created successfully!")
OutputFormatter.key_value("Number", f"#{result['number']}")
OutputFormatter.key_value("Title", result['title'])
OutputFormatter.key_value("Status", result['state'])
if 'html_url' in result:
OutputFormatter.key_value("URL", result['html_url'])
OutputFormatter.empty_line()
print("💡 Next steps:")
print(f" - Use 'make tdd-start NUM={result['number']}' to begin work")
print(f" - Use 'make show-issue NUM={result['number']}' to view details")
2025-09-26 16:35:13 +02:00
@staticmethod
def show_issue_details(issue_data: Dict[str, Any]) -> None:
"""Display comprehensive issue details."""
OutputFormatter.header(f"Issue #{issue_data['number']} Details", "=")
print(f"**Title:** {issue_data['title']}")
print(f"**Status:** {issue_data['state'].upper()}")
print(f"**Number:** #{issue_data['number']}")
print(f"**Created:** {issue_data['created_at'].strftime('%Y-%m-%d %H:%M')}")
print(f"**Updated:** {issue_data['updated_at'].strftime('%Y-%m-%d %H:%M')}")
print(f"**URL:** {issue_data['html_url']}")
if issue_data['assignee']:
print(f"**Assignee:** {issue_data['assignee']}")
OutputFormatter.empty_line()
print("**Project Management:**")
# Milestone information
if issue_data['milestone']:
milestone = issue_data['milestone']
print(f" 📋 Milestone: #{milestone['id']} - {milestone['title']} ({milestone['state']})")
else:
print(" 📋 Milestone: None")
# Project/Board information
print(" 🎯 Project: Getting Started (assumed - requires board API)")
# Labels and state information
print(f" 📊 State: {issue_data['state_label']}")
print(f" 🚨 Priority: {issue_data['priority_label']}")
if issue_data['type_labels']:
type_display = ', '.join(issue_data['type_labels'])
print(f" 🏷️ Type: {type_display}")
if issue_data['other_labels']:
print(f" 🏷️ Other Labels: {', '.join(issue_data['other_labels'])}")
print(f" 📝 Kanban Column: {issue_data['kanban_column']}")
OutputFormatter.empty_line()
print("**Description:**")
print(issue_data['body'])
OutputFormatter.empty_line()
print("💡 Tip: Use 'make list-issues' to see all issues")
@staticmethod
def show_coverage_analysis(coverage_data: Dict[str, Any]) -> None:
"""Display test coverage analysis results."""
OutputFormatter.header(f"Test Coverage Analysis for Issue #{coverage_data['issue_number']}", "=")
print(f"📋 Issue: #{coverage_data['issue_number']} - {coverage_data['issue_title']}")
print(f"📊 Coverage: {coverage_data['coverage_percentage']:.1f}%")
OutputFormatter.empty_line()
# Show requirements analysis
print("🎯 Identified Requirements:")
if coverage_data['requirements']:
for req in coverage_data['requirements']:
priority_icon = {"critical": "🚨", "important": "⚠️", "nice-to-have": "💡"}
icon = priority_icon.get(req.priority, "📝")
print(f" {icon} [{req.priority.upper()}] {req.category}: {req.description}")
else:
print(" No specific requirements detected")
OutputFormatter.empty_line()
# Show existing tests
print("🧪 Existing Test Coverage:")
issue_number = coverage_data['issue_number']
issue_related_tests = [t for t in coverage_data['existing_tests'] if t.related_issue == issue_number]
if issue_related_tests:
for test in issue_related_tests:
test_count = len(test.test_methods)
print(f"{test.file_path.name} ({test_count} test methods)")
if test.test_methods:
for method in test.test_methods[:3]: # Show first 3
print(f" - {method}")
if len(test.test_methods) > 3:
print(f" - ... and {len(test.test_methods) - 3} more")
else:
print(" 📝 No tests specifically for this issue found")
# Show general tests that might be relevant
relevant_tests = [t for t in coverage_data['existing_tests']
if any(keyword in ' '.join(t.coverage_keywords)
for req in coverage_data['requirements']
for keyword in req.keywords)]
if relevant_tests:
print(" 📋 Potentially relevant tests:")
for test in relevant_tests[:3]:
print(f" 📄 {test.file_path.name}")
OutputFormatter.empty_line()
# Show coverage gaps
if coverage_data['coverage_gaps']:
print("❌ Coverage Gaps Found:")
for gap in coverage_data['coverage_gaps']:
priority_icon = {"critical": "🚨", "important": "⚠️", "nice-to-have": "💡"}
icon = priority_icon.get(gap.requirement.priority, "📝")
print(f" {icon} Missing: {gap.requirement.description}")
print(f" 💡 Suggested test: {gap.suggested_test_name}")
print(f" 📄 Suggested file: {gap.suggested_test_file}")
OutputFormatter.empty_line()
else:
print("✅ No significant coverage gaps detected!")
OutputFormatter.empty_line()
# Show recommendations
print("📝 Recommendations:")
for recommendation in coverage_data['recommendations']:
print(f" {recommendation}")
class ProjectView:
"""View for project management information display."""
@staticmethod
def show_setup_success() -> None:
"""Display successful project setup."""
OutputFormatter.success("Project management setup complete!")
print("📋 Available states: todo, active, review, done, blocked")
print("📊 Available priorities: low, medium, high, critical")
@staticmethod
def show_milestone_list(milestones: List[Any]) -> None:
"""Display milestone list."""
OutputFormatter.header("Project Milestones", "=")
if not milestones:
print("No milestones found")
return
for milestone in milestones:
status_icon = "🟢" if milestone.state == "open" else "🔴"
print(f"{status_icon} Milestone #{milestone.id}: {milestone.title}")
print(f" State: {milestone.state.upper()}")
print(f" Issues: {milestone.open_issues} open, {milestone.closed_issues} closed")
if milestone.description:
print(f" Description: {milestone.description}")
if milestone.due_on:
print(f" Due: {milestone.due_on}")
OutputFormatter.empty_line()
@staticmethod
def show_overview(overview: Dict[str, Any]) -> None:
"""Display project overview."""
OutputFormatter.header("Project Management Overview", "=")
OutputFormatter.key_value("Milestones", f"{overview['milestones']} total")
OutputFormatter.key_value("Active Projects", overview['active_projects'], 1)
OutputFormatter.key_value("Completed Projects", overview['completed_projects'], 1)
OutputFormatter.key_value("Total Labels", overview['total_labels'])
ready_status = "✅ Yes" if overview['project_management_ready'] else "❌ No - run setup-project-mgmt"
OutputFormatter.key_value("Project Management Ready", ready_status)