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>
3.9 KiB
Hotfix Design: v1.1.1-rc.2 - Metrics Dashboard Template Data Mismatch
Problem Summary
Production deployment of v1.1.1-rc.1 exposed two critical issues in the metrics dashboard:
- Route Conflict (Fixed in initial attempt): Two routes mapped to similar paths causing ambiguity
- Template/Data Mismatch (Root cause): Template expects different data structure than monitoring module provides
The Template/Data Mismatch
Template Expects (metrics_dashboard.html line 163):
{{ metrics.database.count|default(0) }}
{{ metrics.database.avg|default(0) }}
{{ metrics.database.min|default(0) }}
{{ metrics.database.max|default(0) }}
Monitoring Module Returns:
{
"by_type": {
"database": {
"count": 50,
"avg_duration_ms": 12.5,
"min_duration_ms": 2.0,
"max_duration_ms": 45.0
}
}
}
Note the two mismatches:
- Nesting: Template wants
metrics.databasebut getsmetrics.by_type.database - Field Names: Template wants
avgbut getsavg_duration_ms
Solution: Route Adapter Pattern
Transform data at the presentation layer (route handler) to match template expectations.
Implementation
Added a transformer function in admin.py that:
- Flattens the nested structure (
by_type.database→database) - Maps field names (
avg_duration_ms→avg) - Provides safe defaults for missing data
def transform_metrics_for_template(metrics_stats):
"""Transform metrics stats to match template structure"""
transformed = {}
# Map by_type to direct access with field name mapping
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), # Note field name change
'min': type_data.get('min_duration_ms', 0),
'max': type_data.get('max_duration_ms', 0)
}
else:
# Safe defaults
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
Why This Approach?
- Minimal Risk: Only changes route handler, not core monitoring module
- Preserves API: Monitoring module remains unchanged for other consumers
- No Template Changes: Avoids modifying template and JavaScript
- Clear Separation: Route acts as adapter between business logic and view
Additional Fixes Applied
- Route Path Change:
/admin/dashboard→/admin/metrics-dashboard(prevents conflict) - Defensive Imports: Graceful handling of missing monitoring module
- Error Handling: Safe defaults when metrics collection fails
Testing and Validation
Created comprehensive test script validating:
- Data structure transformation works correctly
- All template fields accessible after transformation
- Safe defaults provided for missing data
- Field name mapping correct
All 32 admin route tests pass with 100% success rate.
Files Modified
-
/starpunk/routes/admin.py:- Lines 218-260: Added transformer function
- Line 263: Changed route path
- Lines 285-314: Applied transformer and added error handling
-
/starpunk/__init__.py: Version bump to 1.1.1-rc.2 -
/CHANGELOG.md: Documented hotfix
Production Impact
Before: 500 error with 'dict object' has no attribute 'database'
After: Metrics dashboard loads correctly with properly structured data
This is a tactical bug fix, not an architectural change, and should be documented as such.