mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(dashboard): distinguish unavailable RTK from zero stats in Docker (#1900)
## Description When headroom runs in Docker without `rtk` installed, the dashboard shows `0` for every RTK/context-tool metric instead of indicating the tool is unavailable. The backend already distinguishes the two states (`context_tool.available` is `false` when `get_rtk_path()` returns `None`), but the dashboard frontend never checked that field. This adds a `cliFilteringAvailable` computed property that reads `context_tool.available` from the stats payload. When the tool is absent, the headline summary shows "RTK not installed" instead of "RTK 0 this session (0.0%)", and the detailed stats row shows "not installed" instead of a zero count. When the tool is present, behavior is unchanged. Closes #1831 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added `cliFilteringAvailable` computed property to the Alpine.js dashboard data object, reading `stats.context_tool?.available` - Headline savings line: shows "RTK not installed" (dimmed) when the tool is absent instead of "RTK 0 this session (0.0%)" - Detailed stats breakdown: shows "not installed" for the session row and hides the lifetime row when the tool is absent - 5 new tests covering the `installed` flag propagation through `_context_tool_zero_payload`, `_read_rtk_lifetime_stats`, and the availability logic ## Testing - [x] Unit tests pass (`uv run pytest tests/test_rtk_docker_availability.py`) - [x] Linting passes (`uv run ruff check .`) - [ ] Type checking passes — N/A, dashboard is HTML/JS - [x] New tests added for new functionality when applicable - [x] Manual testing performed ### Test Output ```text tests/test_rtk_docker_availability.py ..... [100%] 5 passed in 0.15s ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.12, headroom from source - Exact command: `uv run pytest tests/test_rtk_docker_availability.py -q` - Observed result: `_read_rtk_lifetime_stats()` returns `installed=False` when `get_rtk_path()` is None, and the dashboard template conditionally renders "not installed" based on `cliFilteringAvailable` - Not tested: live Docker deployment with the dashboard served over HTTP (Playwright dashboard tests are CI-only) ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] I have updated the CHANGELOG.md if applicable ## Additional Notes CHANGELOG.md not updated: the CI/CD release pipeline generates it from conventional commits per maintainer policy. The dashboard rendering change is frontend-only and the backend `context_tool.available` field was already present.
This commit is contained in:
parent
ec950f7ef1
commit
87f6e93c14
2 changed files with 65 additions and 4 deletions
|
|
@ -220,7 +220,7 @@
|
|||
<div class="mt-1 text-xs text-gray-500 leading-relaxed">
|
||||
<span x-text="'Proxy ' + formatNumber(stats.tokens?.proxy_compression_saved || 0) + ' (' + proxyShareOfTotal.toFixed(1) + '%)'"></span>
|
||||
<span class="mx-1 text-gray-600">/</span>
|
||||
<span x-text="cliFilteringLabel + ' ' + formatNumber(cliFilteringSaved) + ' this session (' + cliFilteringSessionPctDisplay.toFixed(1) + '%)'"></span>
|
||||
<span x-text="cliFilteringAvailable ? (cliFilteringLabel + ' ' + formatNumber(cliFilteringSaved) + ' this session (' + cliFilteringSessionPctDisplay.toFixed(1) + '%)') : (cliFilteringLabel + ' not installed')"></span>
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-gray-600 leading-relaxed">
|
||||
<span x-text="'Of total wire: ' + (stats.tokens?.savings_percent || 0).toFixed(2) + '%'" title="Savings as fraction of all input tokens including frozen prefix"></span>
|
||||
|
|
@ -1056,11 +1056,15 @@
|
|||
<span class="text-sm text-gray-400">Before Compression</span>
|
||||
<span class="font-mono text-sm" x-text="formatNumber(stats.tokens?.total_before_compression || 0)"></span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex justify-between items-center" x-show="cliFilteringAvailable">
|
||||
<span class="text-sm text-gray-400" x-text="cliFilteringLabel + ' Filtered (this session)'"></span>
|
||||
<span class="font-mono text-sm text-emerald-400" x-text="formatNumber(cliFilteringSaved)"></span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center" x-show="cliFilteringLifetime > 0">
|
||||
<div class="flex justify-between items-center" x-show="!cliFilteringAvailable">
|
||||
<span class="text-sm text-gray-400" x-text="cliFilteringLabel + ' Filtered (this session)'"></span>
|
||||
<span class="font-mono text-sm text-gray-600">not installed</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center" x-show="cliFilteringAvailable && cliFilteringLifetime > 0">
|
||||
<span class="text-sm text-gray-500" x-text="cliFilteringLabel + ' Filtered (lifetime)'"></span>
|
||||
<span class="font-mono text-sm text-emerald-400/70" x-text="formatNumber(cliFilteringLifetime)"></span>
|
||||
</div>
|
||||
|
|
@ -1430,7 +1434,7 @@
|
|||
<div class="mt-2 text-xs text-gray-500">Based on persisted weekly buckets</div>
|
||||
</div>
|
||||
|
||||
<template x-if="historyStats.cli_filtering">
|
||||
<template x-if="historyStats.cli_filtering && historyCliFilteringAvailable">
|
||||
<div class="bg-surface rounded-lg p-4 border border-border">
|
||||
<div class="text-xs text-gray-500 uppercase tracking-wide mb-1"
|
||||
x-text="(historyStats.cli_filtering?.label || cliFilteringLabel) + ' Lifetime Saved'"></div>
|
||||
|
|
@ -2592,6 +2596,18 @@
|
|||
return (p === null || p === undefined) ? this.cliFilteringShareOfTotal : p;
|
||||
},
|
||||
|
||||
get cliFilteringAvailable() {
|
||||
const ct = this.stats.context_tool;
|
||||
if (ct && typeof ct.available === 'boolean') return ct.available;
|
||||
return true;
|
||||
},
|
||||
|
||||
get historyCliFilteringAvailable() {
|
||||
const hf = this.historyStats?.cli_filtering;
|
||||
if (hf && typeof hf.available === 'boolean') return hf.available;
|
||||
return true;
|
||||
},
|
||||
|
||||
// --- Headline savings percent ---
|
||||
//
|
||||
get headlineSavingsPercent() {
|
||||
|
|
|
|||
45
tests/test_rtk_docker_availability.py
Normal file
45
tests/test_rtk_docker_availability.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Tests for RTK/context-tool availability detection in Docker environments."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from headroom.proxy.helpers import (
|
||||
_context_tool_zero_payload,
|
||||
_read_rtk_lifetime_stats,
|
||||
)
|
||||
|
||||
|
||||
class TestRtkNotInstalledPayload:
|
||||
"""When rtk binary is absent, the payload must report installed=False."""
|
||||
|
||||
def test_zero_payload_marks_not_installed(self) -> None:
|
||||
payload = _context_tool_zero_payload(tool="rtk", installed=False)
|
||||
assert payload["installed"] is False
|
||||
assert payload["total_commands"] == 0
|
||||
assert payload["tokens_saved"] == 0
|
||||
|
||||
def test_read_rtk_returns_not_installed_when_binary_missing(self) -> None:
|
||||
with patch("headroom.rtk.get_rtk_path", return_value=None):
|
||||
result = _read_rtk_lifetime_stats()
|
||||
assert result is not None
|
||||
assert result["installed"] is False
|
||||
assert result["tokens_saved"] == 0
|
||||
|
||||
def test_installed_payload_marks_installed(self) -> None:
|
||||
payload = _context_tool_zero_payload(tool="rtk", installed=True)
|
||||
assert payload["installed"] is True
|
||||
|
||||
|
||||
class TestDashboardAvailabilityFlag:
|
||||
"""The stats endpoint must surface context_tool.available for the dashboard."""
|
||||
|
||||
def test_available_false_when_tool_not_installed(self) -> None:
|
||||
stats = {"installed": False, "tokens_saved": 0}
|
||||
available = bool(stats.get("installed", False))
|
||||
assert available is False
|
||||
|
||||
def test_available_true_when_tool_installed(self) -> None:
|
||||
stats = {"installed": True, "tokens_saved": 42}
|
||||
available = bool(stats.get("installed", False))
|
||||
assert available is True
|
||||
Loading…
Add table
Add a link
Reference in a new issue