fix: Add data transformer to resolve metrics dashboard template mismatch
Root cause: Template expects flat structure (metrics.database.count) but monitoring module provides nested structure (metrics.by_type.database.count) with different field names (avg_duration_ms vs avg). Solution: Route Adapter Pattern - transformer function maps data structure at presentation layer. Changes: - Add transform_metrics_for_template() function to admin.py - Update metrics_dashboard() route to use transformer - Provide safe defaults for missing/empty metrics data - Handle all operation types: database, http, render Testing: All 32 admin route tests passing Documentation: - Updated implementation report with actual fix details - Created consolidated hotfix design documentation - Architectural review by architect (approved with minor concerns) Technical debt: Adapter layer should be replaced with proper data contracts in v1.2.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -215,6 +215,51 @@ def delete_note_submit(note_id: int):
|
||||
return redirect(url_for("admin.dashboard"))
|
||||
|
||||
|
||||
def transform_metrics_for_template(metrics_stats):
|
||||
"""
|
||||
Transform metrics stats to match template structure
|
||||
|
||||
The template expects direct access to metrics.database.count, but
|
||||
get_metrics_stats() returns metrics.by_type.database.count.
|
||||
This function adapts the data structure to match template expectations.
|
||||
|
||||
Args:
|
||||
metrics_stats: Dict from get_metrics_stats() with nested by_type structure
|
||||
|
||||
Returns:
|
||||
Dict with flattened structure matching template expectations
|
||||
|
||||
Per ADR-060: Route Adapter Pattern for template compatibility
|
||||
"""
|
||||
transformed = {}
|
||||
|
||||
# Map by_type to direct access
|
||||
for op_type in ['database', 'http', 'render']:
|
||||
if 'by_type' in metrics_stats and op_type in metrics_stats['by_type']:
|
||||
type_data = metrics_stats['by_type'][op_type]
|
||||
transformed[op_type] = {
|
||||
'count': type_data.get('count', 0),
|
||||
'avg': type_data.get('avg_duration_ms', 0),
|
||||
'min': type_data.get('min_duration_ms', 0),
|
||||
'max': type_data.get('max_duration_ms', 0)
|
||||
}
|
||||
else:
|
||||
# Provide defaults for missing types or when by_type doesn't exist
|
||||
transformed[op_type] = {
|
||||
'count': 0,
|
||||
'avg': 0,
|
||||
'min': 0,
|
||||
'max': 0
|
||||
}
|
||||
|
||||
# Keep other top-level stats
|
||||
transformed['total_count'] = metrics_stats.get('total_count', 0)
|
||||
transformed['max_size'] = metrics_stats.get('max_size', 1000)
|
||||
transformed['process_id'] = metrics_stats.get('process_id', 0)
|
||||
|
||||
return transformed
|
||||
|
||||
|
||||
@bp.route("/metrics-dashboard")
|
||||
@require_auth
|
||||
def metrics_dashboard():
|
||||
@@ -254,9 +299,19 @@ def metrics_dashboard():
|
||||
pool_stats = {}
|
||||
|
||||
try:
|
||||
metrics_data = get_metrics_stats()
|
||||
raw_metrics = get_metrics_stats()
|
||||
metrics_data = transform_metrics_for_template(raw_metrics)
|
||||
except Exception as e:
|
||||
flash(f"Error loading metrics: {e}", "warning")
|
||||
# Provide safe defaults matching template expectations
|
||||
metrics_data = {
|
||||
'database': {'count': 0, 'avg': 0, 'min': 0, 'max': 0},
|
||||
'http': {'count': 0, 'avg': 0, 'min': 0, 'max': 0},
|
||||
'render': {'count': 0, 'avg': 0, 'min': 0, 'max': 0},
|
||||
'total_count': 0,
|
||||
'max_size': 1000,
|
||||
'process_id': 0
|
||||
}
|
||||
|
||||
try:
|
||||
pool_stats = get_pool_stats()
|
||||
|
||||
Reference in New Issue
Block a user