152 lines
6.2 KiB
Python
152 lines
6.2 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from jinja2 import Template
|
|
|
|
from src.formatters.base import BaseFormatter
|
|
|
|
|
|
HTML_TEMPLATE = """
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Git Insights Report</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; background: #f5f5f5; }
|
|
.container { max-width: 900px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
h1 { color: #333; border-bottom: 2px solid #4a90d9; padding-bottom: 10px; }
|
|
h2 { color: #4a90d9; margin-top: 30px; }
|
|
.metric { display: inline-block; background: #e8f4fd; padding: 15px 25px; border-radius: 6px; margin: 5px; }
|
|
.metric-value { font-size: 28px; font-weight: bold; color: #4a90d9; }
|
|
.metric-label { font-size: 12px; color: #666; text-transform: uppercase; }
|
|
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
|
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
|
th { background: #f8f9fa; font-weight: 600; }
|
|
.trend-increasing { color: #28a745; }
|
|
.trend-decreasing { color: #dc3545; }
|
|
.trend-stable { color: #6c757d; }
|
|
.section { margin: 25px 0; padding: 20px; background: #fafafa; border-radius: 6px; }
|
|
.timestamp { color: #999; font-size: 14px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Git Insights Report</h1>
|
|
<p class="timestamp">Generated: {{ timestamp }}</p>
|
|
|
|
{% if commit_analysis %}
|
|
<div class="section">
|
|
<h2>Commit Analysis</h2>
|
|
<div>
|
|
<div class="metric">
|
|
<div class="metric-value">{{ commit_analysis.total_commits }}</div>
|
|
<div class="metric-label">Total Commits</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value">{{ commit_analysis.unique_authors }}</div>
|
|
<div class="metric-label">Authors</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value">{{ "%.1f"|format(commit_analysis.average_commits_per_day) }}</div>
|
|
<div class="metric-label">Avg/Day</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3>Top Contributors</h3>
|
|
<table>
|
|
<tr><th>Author</th><th>Commits</th><th>Lines +</th><th>Lines -</th></tr>
|
|
{% for author in commit_analysis.top_authors[:5] %}
|
|
<tr>
|
|
<td>{{ author.name }}</td>
|
|
<td>{{ author.commit_count }}</td>
|
|
<td style="color: #28a745;">{{ author.lines_added }}</td>
|
|
<td style="color: #dc3545;">{{ author.lines_deleted }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if velocity_analysis %}
|
|
<div class="section">
|
|
<h2>Velocity Analysis</h2>
|
|
<div>
|
|
<div class="metric">
|
|
<div class="metric-value">{{ velocity_analysis.commits_per_day }}</div>
|
|
<div class="metric-label">Commits/Day</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value">{{ velocity_analysis.commits_per_week }}</div>
|
|
<div class="metric-label">Commits/Week</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value trend-{{ velocity_analysis.velocity_trend }}">{{ velocity_analysis.velocity_trend|capitalize }}</div>
|
|
<div class="metric-label">Trend</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if code_churn_analysis %}
|
|
<div class="section">
|
|
<h2>Code Churn</h2>
|
|
<div>
|
|
<div class="metric">
|
|
<div class="metric-value" style="color: #28a745;">+{{ code_churn_analysis.total_lines_added }}</div>
|
|
<div class="metric-label">Lines Added</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value" style="color: #dc3545;">-{{ code_churn_analysis.total_lines_deleted }}</div>
|
|
<div class="metric-label">Lines Deleted</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-value">{{ code_churn_analysis.net_change }}</div>
|
|
<div class="metric-label">Net Change</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if risky_commit_analysis and risky_commit_analysis.total_risky > 0 %}
|
|
<div class="section">
|
|
<h2>Risky Commits</h2>
|
|
<p>{{ risky_commit_analysis.total_risky }} potentially risky commits detected</p>
|
|
{% if risky_commit_analysis.large_commits %}
|
|
<h3>Large Commits ({{ risky_commit_analysis.large_commits|length }})</h3>
|
|
<table>
|
|
<tr><th>SHA</th><th>Author</th><th>Changes</th></tr>
|
|
{% for commit in risky_commit_analysis.large_commits[:5] %}
|
|
<tr>
|
|
<td>{{ commit.sha }}</td>
|
|
<td>{{ commit.author_name }}</td>
|
|
<td>+{{ commit.additions }} / -{{ commit.deletions }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
class HTMLFormatter(BaseFormatter):
|
|
"""HTML output formatter."""
|
|
|
|
@staticmethod
|
|
def format(data: Any) -> str:
|
|
"""Format data as HTML."""
|
|
template = Template(HTML_TEMPLATE)
|
|
|
|
return template.render(
|
|
timestamp=datetime.now().isoformat(),
|
|
commit_analysis=getattr(data, "commit_analysis", None),
|
|
velocity_analysis=getattr(data, "velocity_analysis", None),
|
|
code_churn_analysis=getattr(data, "code_churn_analysis", None),
|
|
risky_commit_analysis=getattr(data, "risky_commit_analysis", None),
|
|
)
|