[w9-2] Add 90d window, time-window selector, and chart/table toggle (#528)

Backend: widen usage _PeriodType to 24h/7d/30d/90d and add a 90d branch to
_parse_period (daily buckets already cover it). TestParsePeriod pins the
contract per window.

Frontend: UsagePeriod += 90d with a scaleFor helper (replacing 6 inline
ternaries) and 90 daily mock points. One generic SegmentedControl primitive
(reuses Radix Tabs) drives both the metrics time-window selector
(24h/7d/30d/90d) and the per-chart Chart/Table view toggle — one file, two
roles. The Token Usage & Costs tab drops 8 hardcoded '24h' hooks for a
single period state + selector; the stale '(24h)' cost-card parenthetical
goes too. The Performance landing tab gains a TaskStatusChart donut fed by
the status counts already on the page (no new hook). Agent/team bar charts
gain an inline table view.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-15 04:33:50 +02:00
committed by GitHub
co-authored by Renn F
parent 533ea97d01
commit d9084eeb07
12 changed files with 456 additions and 52 deletions
+40 -1
View File
@@ -17,7 +17,7 @@ from unittest.mock import AsyncMock, MagicMock
from uuid import UUID
import pytest
from roboco.services.usage import UsageService
from roboco.services.usage import UsageService, _parse_period
# ---------------------------------------------------------------------------
# Named constants (ruff PLR2004: magic values in comparisons must be named).
@@ -101,6 +101,45 @@ def _service_with_execute(*return_values: object) -> UsageService:
return UsageService(session)
# ---------------------------------------------------------------------------
# _parse_period — period string → (start_dt, hours)
# ---------------------------------------------------------------------------
class TestParsePeriod:
"""_parse_period maps each period string to (start_dt, hours).
90d is the new window (W9-2); the others pin the existing contract so a
future refactor can't silently drop a window.
"""
_HOURS_PER_DAY = 24
def test_24h_default(self) -> None:
start, hours = _parse_period("24h")
assert hours == self._HOURS_PER_DAY
elapsed_h = (datetime.datetime.now(datetime.UTC) - start).total_seconds() / 3600
assert elapsed_h == pytest.approx(self._HOURS_PER_DAY, abs=1)
def test_7d(self) -> None:
_, hours = _parse_period("7d")
assert hours == 7 * self._HOURS_PER_DAY
def test_30d(self) -> None:
_, hours = _parse_period("30d")
assert hours == 30 * self._HOURS_PER_DAY
def test_90d(self) -> None:
start, hours = _parse_period("90d")
assert hours == 90 * self._HOURS_PER_DAY
elapsed_h = (datetime.datetime.now(datetime.UTC) - start).total_seconds() / 3600
assert elapsed_h == pytest.approx(90 * self._HOURS_PER_DAY, abs=1)
def test_unknown_defaults_to_24h(self) -> None:
_, hours = _parse_period("bogus")
assert hours == self._HOURS_PER_DAY
# ---------------------------------------------------------------------------
# get_summary — trend_pct arithmetic
# ---------------------------------------------------------------------------