From a910a3e4802edebffb7254dad7cd95a6b8813271 Mon Sep 17 00:00:00 2001 From: efiten Date: Sat, 17 Oct 2026 21:07:27 +0200 Subject: [PATCH] fix(packets): re-fetch when the observer filter changes (#2241) (#2242) Fixes #2241. ## Problem In grouped mode, ticking an observer on `#/packets` updated the URL and the trigger label but left the table unchanged until a reload. Since #1821 `applyObserverFilter` leaves grouped rows to the server's EXISTS filter, while the observer menu's change handler still only called `renderTableRows()`, which re-renders the rows already loaded without a new `/api/packets` request. ## Change - `public/packets.js`: the observer menu change handler calls `loadPackets()` instead of `renderTableRows()`, so the request carries the new `observer=` set. - `tests/unit/test-observer-menu-interactions.js`: two tests asserting the handler re-fetches, for ticking one observer and for "All Observers". Both failed before the change. ## Verification - `sh test-all.sh`: all suites pass (locally `test-preflight-xss-gate.js` needs a real `python3` on PATH and `PYTHONUTF8=1`, unrelated to this change). - Browser, local server on the freshened e2e fixture: 55 rows; one observer ticked gives 2 rows, all from that observer; back to "All Observers" gives 53 rows. No reload in between. - Reproduced the original bug on an operator's v3.14.0 instance before the change. ## Not done - Playwright E2E suite not run locally. - Perf: one `/api/packets` request per checkbox toggle, the same request a reload or the node filter already makes. No per-item calls. Co-authored-by: Claude Opus 5.5 --- public/packets.js | 3 ++- tests/unit/test-observer-menu-interactions.js | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/public/packets.js b/public/packets.js index 8c5be8b9..a059e7c6 100644 --- a/public/packets.js +++ b/public/packets.js @@ -2026,7 +2026,8 @@ buildObserverMenu(); updateObsTrigger(); updatePacketsUrl(); - renderTableRows(); + // Grouped rows are filtered by the server only (#1821), so re-fetch. + loadPackets(); }); // --- Type multi-select --- diff --git a/tests/unit/test-observer-menu-interactions.js b/tests/unit/test-observer-menu-interactions.js index 7696685a..96becc95 100644 --- a/tests/unit/test-observer-menu-interactions.js +++ b/tests/unit/test-observer-menu-interactions.js @@ -63,12 +63,13 @@ function stubEl(overrides) { const updateObsTrigger = () => calls.push('updateObsTrigger'); const updatePacketsUrl = () => calls.push('updatePacketsUrl'); const renderTableRows = () => calls.push('renderTableRows'); + const loadPackets = () => calls.push('loadPackets'); const fn = new Function( 'e', 'selectedObservers', 'filters', 'localStorage', - 'buildObserverMenu', 'updateObsTrigger', 'updatePacketsUrl', 'renderTableRows', + 'buildObserverMenu', 'updateObsTrigger', 'updatePacketsUrl', 'renderTableRows', 'loadPackets', body ); - fn(target, selectedObservers, filters, localStorage, buildObserverMenu, updateObsTrigger, updatePacketsUrl, renderTableRows); + fn(target, selectedObservers, filters, localStorage, buildObserverMenu, updateObsTrigger, updatePacketsUrl, renderTableRows, loadPackets); } test('change event with no data-obs-id (bubbled from search input) is a no-op', () => { @@ -88,6 +89,24 @@ function stubEl(overrides) { assert(calls.includes('buildObserverMenu'), 'buildObserverMenu should run for a real toggle'); assert(calls.includes('updatePacketsUrl'), 'updatePacketsUrl should run for a real toggle'); }); + + // Grouped mode leaves the observer filter to the server (#1821), so a + // re-render of the already loaded rows changes nothing until a reload. + test('toggling an observer re-fetches packets from the server', () => { + const calls = []; + const filters = {}; + run({ target: { dataset: { obsId: '7' }, checked: true } }, { calls, selectedObservers: new Set(), filters }); + assert.strictEqual(filters.observer, '7'); + assert(calls.includes('loadPackets'), 'loadPackets must run so the server applies the observer filter'); + }); + + test('selecting All Observers re-fetches packets from the server', () => { + const calls = []; + const filters = { observer: '7' }; + run({ target: { dataset: { obsId: '__all__' }, checked: true } }, { calls, selectedObservers: new Set(['7']), filters }); + assert.strictEqual(filters.observer, undefined); + assert(calls.includes('loadPackets'), 'loadPackets must run so the unfiltered set is fetched'); + }); } // --- 2. Clear Filters must reset and re-apply the observer search box ---