Skip to content

Commit 2de9b0b

Browse files
diogodkclaude
andcommitted
refactor(mcp): collapse monitor dashboard helpers and close two unit-test gaps
Perf — uncached dashboard render now runs the per-table monitor CTE 3 times instead of 5 (review feedback #4). ``get_monitor_changes_by_tables`` already had the total in hand from ``list_monitor_table_summaries`` and was throwing it away; ``count_monitor_changes_by_tables`` then re-ran the full CTE (rows + COUNT) just to recover that total. Helper now returns ``(rows, total)`` directly and the count helper is gone. Render code unpacks the tuple. Tests: - ``test_get_monitor_summary_lookback_out_of_range`` is parametrized over the lower bound, upper bound, beyond-upper, and a negative — was only pinning ``lookback=0`` before, so the 1-365 ceiling was untested. - New ``test_parse_monitor_table_sort_rejects_legacy_row_count_desc`` asserts the legacy ``row_count_desc`` name is rejected and the error surfaces the renamed ``row_count_change_desc`` — guards against an accidental revert of the column-rename signal that the dashboard cell shows a delta, not the raw current count. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 32ffc20 commit 2de9b0b

3 files changed

Lines changed: 28 additions & 30 deletions

File tree

testgen/ui/views/monitors_dashboard.py

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def render(
133133
if sort_field and sort_field not in ALLOWED_SORT_FIELDS:
134134
sort_field = None
135135

136-
monitored_tables_page = get_monitor_changes_by_tables(
136+
monitored_tables_page, all_monitored_tables_count = get_monitor_changes_by_tables(
137137
table_group_id,
138138
table_name_filter=table_name_filter,
139139
anomaly_type_filter=anomaly_type_filter,
@@ -142,11 +142,6 @@ def render(
142142
limit=int(items_per_page),
143143
offset=page_start,
144144
)
145-
all_monitored_tables_count = count_monitor_changes_by_tables(
146-
table_group_id,
147-
table_name_filter=table_name_filter,
148-
anomaly_type_filter=anomaly_type_filter,
149-
)
150145
monitor_changes_summary = summarize_monitor_changes(table_group_id)
151146

152147
monitored_table_names = {table["table_name"] for table in monitored_tables_page}
@@ -358,39 +353,27 @@ def get_monitor_changes_by_tables(
358353
sort_order: Literal["asc"] | Literal["desc"] | None = None,
359354
limit: int | None = None,
360355
offset: int | None = None,
361-
) -> list[dict]:
356+
) -> tuple[list[dict], int]:
362357
"""Per-monitored-table summaries shaped for the dashboard's JSON payload.
363358
364-
Returns plain dicts (rather than ``MonitorTableSummary`` dataclasses) because the
365-
monitor-dashboard widget consumes the payload via ``make_json_safe``. Each row is
366-
augmented with ``table_group_id`` to match the historical payload shape.
359+
Returns ``(rows, total)`` so the dashboard can fill its pager without an extra
360+
round-trip to the model (which would re-run the heavy CTE twice — once for the
361+
rows, once for the count — just to throw the rows away). Rows are dicts (rather
362+
than ``MonitorTableSummary`` dataclasses) because the monitor-dashboard widget
363+
consumes the payload via ``make_json_safe``. Each row is augmented with
364+
``table_group_id`` to match the historical payload shape.
367365
"""
368366
page = 1 + (offset // limit) if limit and offset else 1
369-
summaries, _total = TableGroup.list_monitor_table_summaries(
367+
summaries, total = TableGroup.list_monitor_table_summaries(
370368
table_group_id,
371369
anomaly_types=_dashboard_anomaly_types(anomaly_type_filter),
372370
sort_by=_dashboard_sort_to_model(sort_field, sort_order),
373371
table_name_filter=table_name_filter,
374372
page=page,
375373
limit=limit or 1000,
376374
)
377-
return [{**dataclasses.asdict(s), "table_group_id": table_group_id} for s in summaries]
378-
379-
380-
@st.cache_data(show_spinner=False)
381-
def count_monitor_changes_by_tables(
382-
table_group_id: str,
383-
table_name_filter: str | None = None,
384-
anomaly_type_filter: list[str] | None = None,
385-
) -> int:
386-
_items, total = TableGroup.list_monitor_table_summaries(
387-
table_group_id,
388-
anomaly_types=_dashboard_anomaly_types(anomaly_type_filter),
389-
table_name_filter=table_name_filter,
390-
page=1,
391-
limit=1,
392-
)
393-
return total
375+
rows = [{**dataclasses.asdict(s), "table_group_id": table_group_id} for s in summaries]
376+
return rows, total
394377

395378

396379
@st.cache_data(show_spinner=False)

tests/unit/mcp/test_tools_common.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,18 @@ def test_parse_monitor_table_sort_rejects_unknown():
902902
assert valid in msg
903903

904904

905+
def test_parse_monitor_table_sort_rejects_legacy_row_count_desc():
906+
"""Guard against the pre-review-feedback ``row_count_desc`` name accidentally
907+
coming back: the rename to ``row_count_change_desc`` is the canonical signal
908+
that the column shows a delta, not the raw current count. Drop this only when
909+
introducing a deliberate replacement."""
910+
from testgen.mcp.tools.common import parse_monitor_table_sort
911+
912+
with pytest.raises(MCPUserError, match="Invalid sort_by") as exc:
913+
parse_monitor_table_sort("row_count_desc")
914+
assert "row_count_change_desc" in str(exc.value)
915+
916+
905917
def test_resolve_monitored_table_group_returns_suite():
906918
from testgen.common.models.table_group import TableGroup
907919
from testgen.common.models.test_suite import TestSuite

tests/unit/mcp/test_tools_monitors.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,15 @@ def test_get_monitor_summary_lookback_override_applied(mock_resolve, mock_tg_cls
196196
assert "**Lookback:** 14 runs (override)" in out
197197

198198

199-
def test_get_monitor_summary_lookback_out_of_range(db_session_mock):
199+
@pytest.mark.parametrize("bad_lookback", [0, 366, 500, -1])
200+
def test_get_monitor_summary_lookback_out_of_range(bad_lookback, db_session_mock):
201+
"""Both bounds (and beyond) are pinned — the model accepts 1..365 inclusive."""
200202
from testgen.mcp.tools.monitors import get_monitor_summary
201203

202204
with _patch_perms(), pytest.raises(MCPUserError) as exc:
203-
get_monitor_summary(str(uuid4()), lookback=0)
205+
get_monitor_summary(str(uuid4()), lookback=bad_lookback)
204206
assert "between 1 and 365" in str(exc.value)
207+
assert f"`{bad_lookback}`" in str(exc.value)
205208

206209

207210
@patch(f"{MODULE}.next_scheduled_run", return_value=None)

0 commit comments

Comments
 (0)