Skip to content

Add an advanced filter panel to the jobs view - #9787

Open
camd wants to merge 29 commits into
masterfrom
camd/advanced-filter-panel
Open

Add an advanced filter panel to the jobs view#9787
camd wants to merge 29 commits into
masterfrom
camd/advanced-filter-panel

Conversation

@camd

@camd camd commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

What this adds

An Advanced Filters popover for the jobs view that brings every filter dimension into one place. It replaces the old one-field-at-a-time filter form that appeared under the secondary toolbar (that inline form is removed; the active-filter chips bar remains).

The panel is opened from a new sliders icon attached to the left edge of the "Filter platforms & jobs" quick-filter box, from Filters → Advanced Filters… in the top nav, or with the shift+f keyboard shortcut (documented in the ? shortcut overlay). The trigger shows a count badge when any non-default filters are active.

Screenshot 2026-08-16 at 11 02 06 AM

Panel contents (top to bottom)

  • Push range (staged) — quick presets (last 1/2/7/14 days), side-by-side start/end date pickers, and author/revision inputs with autocomplete. Author and revision suggestions come from the pushes already in memory plus one lazy, per-repo-cached fetch of the last 4 days of pushes. Nothing takes effect until Apply (these params reload pushes from the server); Apply stays disabled and the bold STAGED hint stays hidden until a field actually differs from the URL. Pressing Enter anywhere in the panel applies a dirty push range. Author/revision inputs get an inline ⊗ to clear them.
  • Field filters (live) — add/edit/remove filters for platform, job name/symbol, group name/symbol, and test path, with <datalist> typeahead sourced from the currently loaded jobs.
  • Tier / classified state / failure classification (live) — pill toggles and a classification dropdown.
  • Presets — name and save the current filter combination to localStorage, re-apply it with one click on any repo (the repo param is intentionally not part of a preset), delete with ⊗. Enter in the name box saves.
  • Result status (live) — a pill per status plus failures / in progress group shortcuts. Pills use the same status colors as the toolbar chicklets (shared CSS tokens): filled when active, outlined when off.

Everything "live" reads and writes through the existing FilterModel, so the URL stays the single source of truth and the panel can never disagree with the toolbar chicklets, tier menu, or chips bar. Only the push-range section holds local state, and only until Apply.

Discoverability

  • One-time coach mark on first render ("New: advanced filters…") dismissed forever by Got it or by opening the panel any way (localStorage flag).
  • shift+f completes the existing family: f focuses the quick filter, ctrl+shift+f clears it.
  • Active-filter count badge on the trigger.

Implementation notes

  • New components live in ui/job-view/headerbars/filter-panel/; the popover is a react-bootstrap Overlay/Popover with rootClose (outside click / Esc closes), focus moved into the panel on open and restored to the trigger on close.
  • The old isFieldFilterVisible plumbing through AppPrimaryNavBar was repurposed as isFilterPanelOpen/toggleFilterPanel so both the trigger and the hotkey share one source of truth. The PrimaryNavBar React.memo comparator gained the new props (a miss there made the panel unopenable — now covered by a regression test through the memoized export).
  • ActiveFilters is chips-only now; startdate/enddate URL params (already supported by the fetch layer) finally have a UI.
  • Status colors were hoisted to shared :root tokens used by both the toolbar chicklets and the panel pills.

Testing

  • ~45 new unit tests across the panel (helpers, sections, panel wiring, hotkey, coach mark, trigger/badge, FiltersMenu entry), full suite green: 101 suites / 1103 tests.
  • Manually smoke-tested against the stage backend: open/close via trigger, hotkey, Esc, and outside click; live status filtering; staged date/author apply reloading pushes; presets round-trip; badge and chips staying in sync; no console errors.

Follow-ups (out of scope here)

  • Fold the redundant top-nav FiltersMenu items into built-in presets and remove the menu (its "Advanced Filters…" entry is this panel's third entry point in the meantime; "Pin all showing" needs rehoming first).
  • fromchange/tochange have no panel UI yet (they still count toward the badge and clear with Clear all).

@netlify

netlify Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploy Preview for treeherder ready!

Name Link
🔨 Latest commit e89c0b5
🔍 Latest deploy log https://app.netlify.com/projects/treeherder/deploys/6a89db439be436000830c0f0
😎 Deploy Preview https://deploy-preview-9787--treeherder.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@camd
camd requested a review from gmierz August 16, 2026 18:01
@camd camd self-assigned this Aug 16, 2026
@camd camd added the front-end label Aug 16, 2026
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.36145% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.56%. Comparing base (07be574) to head (c5be746).

Files with missing lines Patch % Lines
...iew/headerbars/filter-panel/FieldFilterSection.jsx 78.00% 8 Missing and 3 partials ⚠️
...derbars/filter-panel/TierClassificationSection.jsx 71.42% 6 Missing ⚠️
...ob-view/headerbars/filter-panel/PresetsSection.jsx 83.87% 3 Missing and 2 partials ⚠️
ui/job-view/KeyboardShortcuts.jsx 50.00% 2 Missing ⚠️
...-view/headerbars/filter-panel/PushRangeSection.jsx 97.26% 2 Missing ⚠️
ui/job-view/App.jsx 80.00% 1 Missing ⚠️
ui/job-view/headerbars/FiltersMenu.jsx 66.66% 1 Missing ⚠️
ui/job-view/headerbars/SecondaryNavBar.jsx 95.23% 1 Missing ⚠️
...ew/headerbars/filter-panel/AdvancedFilterPanel.jsx 96.55% 1 Missing ⚠️
...job-view/headerbars/filter-panel/StatusSection.jsx 92.85% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff             @@
##           master    #9787       +/-   ##
===========================================
+ Coverage   69.15%   83.56%   +14.40%     
===========================================
  Files         637      648       +11     
  Lines       38505    38830      +325     
  Branches     3466     3543       +77     
===========================================
+ Hits        26630    32447     +5817     
+ Misses      11481     5986     -5495     
- Partials      394      397        +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@camd
camd force-pushed the camd/advanced-filter-panel branch from c5be746 to 20a9e46 Compare August 22, 2026 17:24
camd added 22 commits August 22, 2026 10:24
- PrimaryNavBar's memo comparator now compares isFilterPanelOpen and
  classificationTypes so the panel can actually open in the app.
- SecondaryNavBar ignores Overlay rootClose events that originate on the
  trigger button, so the trigger toggles open/closed instead of the two
  handlers canceling each other out.
- AdvancedFilterPanel moves focus into the popover when it opens and
  restores focus to the trigger when it closes.
- Advanced-filter badge uses Bootstrap 5 classes; trigger button gets
  aria-controls/aria-haspopup; PresetsSection falls back to thDefaultRepo;
  FieldFilterSection's remove button gets a matching aria-label.
- Adds regression tests for the memo/re-render bug and the trigger
  open/close toggle bug.
camd added 7 commits August 22, 2026 10:24
The panel now renders full-width between the navbars and the push list,
so opening it pushes the pushes down instead of overlaying them. The
job view root becomes a flex column so the banner takes natural height
and the push-list/details panels split the remainder.

Sections flow left-to-right across the banner and wrap on narrow
viewports, with Presets moved to the end of the row so it sits at the
right edge of the window. Escape closes the banner; outside clicks no
longer do, since it is persistent chrome rather than a transient
popover.
Holding more than that can crash the browser. fetchPushes now refuses
to fetch past the cap (with a notification pointing at the filter
panel), and caps each request's count to the remaining capacity.
Polling stops pulling in new pushes at the cap but keeps fetching jobs
for the pushes already loaded. As a safety net, addPushes truncates to
the newest 500 in case a response overshoots the remaining capacity.

PushModel.getList now respects an explicit caller count on range
queries instead of silently bumping it to the per-request maximum, and
clamps any count to that maximum.
The Active Filters bar now renders between the navbars and the
Advanced Filters banner instead of inside the push-list panel, and the
banner header uses the same alert-info light blue as the Active
Filters bar so the two read as one group. The header's Clear all and
close controls switch to dark-on-light styling to stay legible.
Use a darker shade of the Active Filters alert-info blue as the
background of the whole banner (header and the area behind the section
cards) rather than matching it exactly, add a border between the
Active Filters bar and the banner, and center the Advanced Filters
title at the same 14px size as the Active Filters title with the
Clear all / close controls pinned to the right edge.
The server orders pushes newest-first before applying count, so a
range query (startdate/fromchange) now requests the full remaining
capacity in a single call instead of 100 at a time: for a range of 2
to 10 days ago it loads the 500 pushes closest to the 2-days-ago end,
or the whole range when it holds fewer. getList's clamp rises from the
old per-request maximum to the overall push ceiling; the API itself
caps counts at 1000.
Loading a wide push range (up to the 500-push cap) surfaced three
problems:

- Every Push component fired its jobs fetch on mount, so 500 mounted
  at once meant 500 concurrent API requests (502s from the server) and
  a burst of resolutions whose setState cascade tripped React's
  'Maximum update depth exceeded' guard. A shared task limiter now
  runs at most 6 per-push jobs fetches at a time.
- Each completed jobs fetch recalculated the store-wide unclassified
  counts, re-rendering every subscribed component per push. The
  recalculation is now debounced to coalesce bursts.
- Polling fetched new jobs with one request listing every loaded push
  id; past ~440 ids the encoded query string exceeded the server's
  4kB request-line limit (400 Bad Request). The ids are now chunked
  100 per request.
…nding pill

The Active Filters bar already shows which filters are set, so the
count badge on the toolbar trigger is redundant; remove it along with
the now-unused getActiveFilterCount helper. Give the banner header a
min-height so the absolutely-centered Clear all button clears the top
edge instead of touching it. Darken the result-status pending pill's
outline color — the real pending gray is too faint on a white card —
while keeping it lighter than running.
@camd
camd force-pushed the camd/advanced-filter-panel branch from 20a9e46 to e89c0b5 Compare August 22, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants