"""
Report Generator Module - Generate HTML and JSON reports.
This module handles:
- HTML dashboard generation
- JSON report output
- Console summary formatting
- Report history management
"""
import json
import logging
from pathlib import Path
from typing import Dict, Any
from datetime import datetime
logger = logging.getLogger(__name__)
class ReportGenerator:
"""Generate quality reports in multiple formats."""
def __init__(self, config: Dict[str, Any], project_root: str):
"""
Initialize the report generator.
Args:
config: Configuration dictionary for report generator
project_root: Root directory of the project
"""
self.config = config
self.project_root = Path(project_root)
self.output_dir = self.project_root / config.get("output_dir", "quality_reports")
self.output_dir.mkdir(exist_ok=True)
def generate_reports(self, results: Dict[str, Any]):
"""
Generate all configured reports.
Args:
results: Combined results from all modules
"""
logger.info("Generating reports...")
# Generate JSON report
if self.config.get("generate_json", True):
self._generate_json_report(results)
# Generate HTML report
if self.config.get("generate_html", True):
self._generate_html_report(results)
# Generate console summary
if self.config.get("generate_console_summary", True):
self._print_console_summary(results)
# Save to history
if self.config.get("save_history", True):
self._save_to_history(results)
logger.info(f"Reports generated in: {self.output_dir}")
def _generate_json_report(self, results: Dict[str, Any]):
"""Generate JSON report."""
logger.info("Generating JSON report...")
report_file = self.output_dir / "latest_report.json"
report_data = {
"timestamp": datetime.now().isoformat(),
"project_name": self.config.get("project_name", "Unknown"),
"results": results
}
with open(report_file, 'w') as f:
json.dump(report_data, f, indent=2)
logger.info(f"JSON report saved to: {report_file}")
def _generate_html_report(self, results: Dict[str, Any]):
"""Generate HTML dashboard report."""
logger.info("Generating HTML report...")
report_file = self.output_dir / "latest_report.html"
# Extract results
cleaner = results.get("code_cleaner", {})
vuln = results.get("vulnerability_checker", {})
conn = results.get("connection_tester", {})
tester = results.get("code_tester", {})
# Build HTML
html = f"""
Code Quality Report - {self.config.get('project_name', 'Project')}
š Code Cleaning
Files Processed
{cleaner.get('total_files_processed', 0)}
Formatted
{len(cleaner.get('formatted_files', []))}
Errors
{len(cleaner.get('errors', []))}
š Security
Total Issues
{vuln.get('total_issues', 0)}
Critical
{vuln.get('severity_counts', {}).get('CRITICAL', 0)}
High
{vuln.get('severity_counts', {}).get('HIGH', 0)}
š Connections
Total Tests
{conn.get('total_tests', 0)}
Passed
{conn.get('passed_tests', 0)}
Failed
{conn.get('failed_tests', 0)}
ā
Tests
Total Tests
{tester.get('total_tests', 0)}
Coverage
{tester.get('coverage_percentage', 0):.1f}%
Pylint Score
{tester.get('pylint_results', {}).get('score', 'N/A')}/10
š Security Vulnerabilities
{self._generate_vulnerability_html(vuln)}
š Connection Tests
{self._generate_connection_html(conn)}
"""
with open(report_file, 'w', encoding='utf-8') as f:
f.write(html)
logger.info(f"HTML report saved to: {report_file}")
def _generate_vulnerability_html(self, vuln: Dict[str, Any]) -> str:
"""Generate HTML for vulnerability section."""
if vuln.get('total_issues', 0) == 0:
return 'ā No security issues found!
'
html = ''
# Bandit issues
for issue in vuln.get('bandit_issues', [])[:10]: # Limit to 10
severity = issue.get('severity', 'LOW').lower()
html += f'''
-
{issue.get('severity', 'LOW')}
{issue.get('issue', 'Unknown issue')}
{issue.get('file', '')}:{issue.get('line', '')}
'''
# Custom checks
for issue in vuln.get('custom_checks', []):
severity = issue.get('severity', 'LOW').lower()
html += f'''
-
{issue.get('severity', 'LOW')}
{issue.get('issue', 'Unknown issue')}
{issue.get('file', '')}
{issue.get('recommendation', '')}
'''
html += '
'
return html
def _generate_connection_html(self, conn: Dict[str, Any]) -> str:
"""Generate HTML for connection tests section."""
if conn.get('total_tests', 0) == 0:
return 'No connection tests were run.
'
html = ''
# Database tests
for test in conn.get('database_tests', []):
status = test.get('status', 'FAILED').lower()
html += f'''
{test.get('test', 'Database Test')}
{test.get('status', 'UNKNOWN')}
'''
# API tests
for test in conn.get('api_tests', [])[:5]: # Limit to 5
status = test.get('status', 'FAILED').lower()
html += f'''
{test.get('test', 'API Test')}
{test.get('status', 'UNKNOWN')}
'''
return html
def _print_console_summary(self, results: Dict[str, Any]):
"""Print a summary to console."""
print("\n" + "="*70)
print("CODE QUALITY REPORT SUMMARY")
print("="*70)
# Code Cleaner
cleaner = results.get("code_cleaner", {})
print(f"\nš CODE CLEANING:")
print(f" Files processed: {cleaner.get('total_files_processed', 0)}")
print(f" Formatted: {len(cleaner.get('formatted_files', []))}")
# Vulnerabilities
vuln = results.get("vulnerability_checker", {})
print(f"\nš SECURITY:")
print(f" Total issues: {vuln.get('total_issues', 0)}")
print(f" Critical: {vuln.get('severity_counts', {}).get('CRITICAL', 0)}")
print(f" High: {vuln.get('severity_counts', {}).get('HIGH', 0)}")
# Connections
conn = results.get("connection_tester", {})
print(f"\nš CONNECTIONS:")
print(f" Tests run: {conn.get('total_tests', 0)}")
print(f" Passed: {conn.get('passed_tests', 0)}")
print(f" Failed: {conn.get('failed_tests', 0)}")
# Tests
tester = results.get("code_tester", {})
print(f"\nā
CODE TESTS:")
print(f" Total tests: {tester.get('total_tests', 0)}")
print(f" Coverage: {tester.get('coverage_percentage', 0):.1f}%")
print(f" Pylint score: {tester.get('pylint_results', {}).get('score', 'N/A')}/10")
print("\n" + "="*70)
print(f"Full reports available in: {self.output_dir}")
print("="*70 + "\n")
def _save_to_history(self, results: Dict[str, Any]):
"""Save report to history."""
history_dir = self.output_dir / "history"
history_dir.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
history_file = history_dir / f"report_{timestamp}.json"
with open(history_file, 'w') as f:
json.dump(results, f, indent=2)
# Clean old history files
max_history = self.config.get("max_history_reports", 10)
history_files = sorted(history_dir.glob("report_*.json"))
if len(history_files) > max_history:
for old_file in history_files[:-max_history]:
old_file.unlink()