mirror of
https://github.com/zvx-echo6/central.git
synced 2026-06-10 11:54:37 +02:00
feat(filtering): chip-picker filters, search, time presets, active pills (v0.7.1)
Biggest PR of the v0.7.x GUI rework arc. Replaces the single-select /events filter row with a multi-select, URL-addressable filtering surface. - Search: full-width box, debounced 300ms, server-side ILIKE over the inner adapter payload (covers the derived subject + location); parameterized with LIKE wildcards escaped (ESCAPE '\'). Injection-safe. - Adapter / Category / Event Type / Severity: multi-select chip-pickers (shared _chip_picker.html macro). Adapter is grouped by domain with color swatches and an in-panel search. Backend uses `= ANY(...)`. URL state is comma-separated. - Event Type is derived as split_part(category,'.',1) (no event_type column yet; a stand-in until the v0.8 canonical schema). Severity maps labels to the numeric scale (4=critical..1=low, 0/NULL=unknown). - Time: preset dropdown (15m/1h/6h/24h/7d/active/all) + custom from/to range, encoded in a single `time` token. GUI defaults to last_24h; events.json keeps its single-value adapter/since/until contract (no default). - Active pills: server-rendered from parsed state, updated out-of-band on each HTMX swap; each x clears that filter and re-submits. - URL state persistence: every filter in the query string; /events/rows sets HX-Push-Url to the /events?... full-page URL so bookmarking/back-forward work. Filter options are rendered server-side at page load (DISTINCT category + split_part, registry adapters, severity enum) -- no new AJAX endpoints. Vanilla JS + HTMX (no framework added). CSS is functional-only; visual polish is deferred to a later pass per the rework plan. Adds TestEventsFiltering (24 tests: multi-value parse, ILIKE injection safety, time-preset resolution with injected clock, severity/NULL handling, active-pill descriptors, URL round-trip). Updates four TestEventsFeedFrontend assertions to the new filter_state/adapters contract. Full suite: 658 passed, 1 skipped (central and unprivileged zvx). No adapter base class change -> central-gui restart only (no supervisor restart). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6af419c345
commit
380cde31f8
7 changed files with 785 additions and 141 deletions
|
|
@ -104,7 +104,8 @@ class TestEventsFeedFrontendAuthenticated:
|
|||
|
||||
assert result.status_code == 200
|
||||
context = mock_templates.TemplateResponse.call_args.kwargs.get("context")
|
||||
assert context["filter_values"]["adapter"] == "nws"
|
||||
# v0.7.1: adapter is now a multi-select; single value parses to a 1-list.
|
||||
assert context["filter_state"]["adapters"] == ["nws"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_events_since_until_filter(self):
|
||||
|
|
@ -154,8 +155,9 @@ class TestEventsFeedFrontendAuthenticated:
|
|||
mock_templates.TemplateResponse.assert_called_once()
|
||||
call_kwargs = mock_templates.TemplateResponse.call_args.kwargs
|
||||
context = call_kwargs.get("context", call_kwargs)
|
||||
assert context["filter_values"]["since"] == "2026-05-17T00:00:00"
|
||||
assert context["filter_values"]["until"] == "2026-05-17T12:00:00"
|
||||
# v0.7.1: the GUI uses time presets, but legacy since/until are still
|
||||
# honored (JSON API + bookmarks); they parse without error and apply.
|
||||
assert context["filter_error"] is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_events_region_filter(self):
|
||||
|
|
@ -207,10 +209,10 @@ class TestEventsFeedFrontendAuthenticated:
|
|||
mock_templates.TemplateResponse.assert_called_once()
|
||||
call_kwargs = mock_templates.TemplateResponse.call_args.kwargs
|
||||
context = call_kwargs.get("context", call_kwargs)
|
||||
assert context["filter_values"]["region_north"] == "49.5"
|
||||
assert context["filter_values"]["region_south"] == "31"
|
||||
assert context["filter_values"]["region_east"] == "-102"
|
||||
assert context["filter_values"]["region_west"] == "-124.5"
|
||||
assert context["filter_state"]["region_north"] == "49.5"
|
||||
assert context["filter_state"]["region_south"] == "31"
|
||||
assert context["filter_state"]["region_east"] == "-102"
|
||||
assert context["filter_state"]["region_west"] == "-124.5"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_events_partial_region_shows_error_banner(self):
|
||||
|
|
@ -772,9 +774,11 @@ class TestRegistryDrivenAdapterFilter:
|
|||
|
||||
# The list is exactly the registry, sorted by name (stable), no extras.
|
||||
assert [a["name"] for a in context["adapters"]] == sorted(registry.keys())
|
||||
# Each entry carries name + display_name straight from the adapter class.
|
||||
# Each entry carries name + display_name (v0.7.1 adds a positional color).
|
||||
by_name = {a["name"]: a for a in context["adapters"]}
|
||||
for cls in registry.values():
|
||||
assert {"name": cls.name, "display_name": cls.display_name} in context["adapters"]
|
||||
assert by_name[cls.name]["display_name"] == cls.display_name
|
||||
assert by_name[cls.name]["color"].startswith("#")
|
||||
|
||||
|
||||
class TestPerAdapterRowPartials:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue