Staging#214
Conversation
Removes grid-level disableColumnMenu from every DataControlHeader page so each column header exposes its kebab menu (Sort, Filter, Hide, Manage). The per-column Filter entry is the "column-based search" users relied on to filter a single column in one click — the global Filter & Columns popover remains for multi-row use. In DataControlHeader, sync grid-filter-model changes that originate outside the toolbar (e.g. column-menu Filter) into the committed registry, so the "Filter & Columns (N)" badge stays accurate and the search box no longer wipes the column-menu filter. The sync has a guard so it doesn't fight Community Edition's auto-truncation after a multi-row toolbar save. Per-column disableColumnMenu: true on action/icon columns is preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each filterable column header now shows an always-visible search icon (left of the on-hover kebab menu). Clicking it opens a small popover with a TextField that filters just that column — much faster than going through Filter & Columns → pick column → contains → type. DataControlHeader's committed-filter registry gets a tiny pub/sub so the per-column QuickSearchHeader (which writes to the shared registry without prop access to the toolbar) can notify ToolbarFilterEditor to re-sync its ref and refresh the "Filter & Columns (N)" badge count. An onApplyFilterModel registry (apiRef → callback) lets the per-column popover dispatch to the page's server-filter handler without prop drilling. Per-column filters coexist with the global search box and the multi- row Filter & Columns editor: all three write to the same registry, and on Community Edition (which truncates filterModel.items to one entry) the quick-column filter takes the active slot so the visible behavior matches the user's most recent action. Wraps consumer columns with the new withQuickSearchHeaders helper in every page/component that uses DataControlHeader. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… access during render
Previously each per-column magnifying-glass filter replaced the previous one — typing in column B wiped column A's filter. Two causes: 1. DataControlHeader's QuickSearchHeader called apiRef.setFilterModel with items.slice(0, 1) after onApply. That re-fires the page's onFilterModelChange with the truncated single item and races whatever state onApply just committed. Skip the grid round-trip entirely when onApply is registered; the page (server fetch or local hook) owns the multi-item AND. Bare client-side grids without a page handler fall back to the truncated setFilterModel (Community Edition can only honor one filter item). 2. The lazy client-side consumer pages (model, fba, gapfill, genome, ReactionKnockoutsDialog) had no page-level filter handler, so the DataGrid's Community filter engine was the only thing filtering rows — and it sees at most one item. Wire each grid through useToolbarGridFiltering (which ANDs items locally via filterDocsByGridModel) and pass filterModel / onFilterModelChange / onApplyFilterModel so per-column quick filters and the Filter & Columns multi-row editor both produce true AND filtering. For the model page, factor the per-tab DataGrid into a small ModelTabDataGrid component so the hook lives at component top level (rules of hooks) and the per-tab filter state is naturally isolated. Filters are applied to the full row set before sort/paginate so the visible page reflects the full filtered count. Server-side reference-data pages (compounds, reactions, genomes, Annotations, list-media, my-jobs, my-models, myMedia, Patric/Rast genome tables) are unchanged at the page level but now benefit from the cleaner dispatch — their existing toolbarSaveRef guard is no longer racing the truncated round-trip, since it no longer happens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous debounced auto-apply re-rendered the parent page mid-typing, which made the column header (and the open Popover with it) remount and discard the in-flight draft text — observed by the user as "I can only type one letter and it's replaced by the next." Switch to an explicit-commit model: - Typing only updates local draft state — no parent re-render until commit - Enter commits the filter and closes the popover - Escape / click-outside cancels (closes without applying) - The X icon clears the column's filter - Reopening the icon seeds the draft with the currently-applied value so the user can extend or replace it Multiple per-column filters AND together: each Enter adds (or replaces) this column's entry in the shared committedFilterRegistry under a predictable id (quick-col-<field>); the full multi-item model goes through onApplyFilterModel so server pages re-fetch and client pages (via useToolbarGridFiltering) re-derive filteredRows with all items ANDed. The toolbar Filter & Columns badge stays in sync via the existing committedFilter pub/sub. Also memoize the wrapped-columns output by input identity so unrelated parent re-renders (data refresh, loading state flips) don't force MUI to rebuild the column header tree and tear down an open popover. ToolbarSearchField (the global search box) and ToolbarFilterEditor (Filter & Columns) are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…over The Filter & Columns popover wasn't reflecting per-column quick filters because ToolbarFilterEditor mirrored the committed registry into a mutable ref (committedItemsRef). Refs don't trigger re-renders, and openEditor was reading the ref — so the badge count and the loaded draft rows could go stale relative to the registry whenever the registry was updated by a sibling (the per-column QuickSearchHeader Enter commit, the column- menu Filter sync) and the ref-update path had any timing slack. Replace the ref pair (committedItemsRef + committedLogicOperatorRef + manual forceUpdate ticks) with a single useState mirror keyed by the committedFilterRegistry pub/sub. Any setCommittedFilter call from any source — per-column header, toolbar save, column-menu sync — now drives a setState and the badge label re-renders automatically. openEditor also reads the registry directly as a belt-and-suspenders source of truth, so the popover always loads the latest per-column filters as editable rows. Net effect: applying a quick per-col filter immediately bumps the "Filter & Columns (N)" badge and shows up as a row when the user opens the popover; editing or X-ing that row in the popover and saving propagates back to the per-column icon's active-state highlight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…icon When a column has any active filter (added via the quick search popover OR via the Filter & Columns editor), the magnifying-glass icon now: - Switches to a filled FilterAltIcon - Gets a primary-color background pill with white icon - Gets a small primary-color dot badge in the corner (extra glanceable) - Shows a tooltip describing the applied operator + value - The popover's helper text echoes the same summary Active-state matching now considers ALL filter items on the column's field, not just those added via the quick-search id, so filters added through the toolbar editor also light up the column's icon. Row count in the toolbar (CustomPagination's "1-25 of N") already reflects each filter change automatically: - Server-side pages: the rowCount prop = numFound and refetches - Client-side useToolbarGridFiltering pages: rows.length = filteredRows.length - Model lazy tabs: rowCount = preparedRows.length (sort(filteredRows)) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The R extends Record<string, unknown> constraint rejected typed row interfaces (Compound, Reaction, ModelReaction, etc.) because they lack an implicit index signature, causing the generic to fall back to the default and return GridColDef<Record<string, unknown>>[]. That then forced DataGrid's R to infer as Record<string, unknown>, cascading into rows/getRowId type errors across many pages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a per-column quick-search header (magnifying-glass icon + popover) to MUI DataGrid usage across the app via a new withQuickSearchHeaders helper, and wires more grids into the shared useToolbarGridFiltering hook so column filters AND together correctly under MUI Community Edition's single-filter truncation. DataControlHeader is refactored to drive the badge label from a state-mirrored, pub/sub-backed shared filter registry, and the FBA results page and ModelDetailPage (via a new ModelTabDataGrid wrapper) gain controlled filter models.
Changes:
- New
withQuickSearchHeadersandQuickSearchHeaderinDataControlHeader.tsx, plusonApplyFilterModelRegistryand pub/sub on the committed-filter registry to share state across toolbar editor, header icons, and the page. - All major data grids (biochem, genomes, list-media, my-models, my-jobs, myMedia, gapfill, genome, fba, model, ReactionKnockoutsDialog, Rast/Patric genomes) now wrap their columns with
withQuickSearchHeadersand (where applicable) integrateuseToolbarGridFiltering; redundantdisableColumnMenuprops removed. app/model/[...path]/page.tsxextracts aModelTabDataGridwrapper that owns per-tabuseToolbarGridFilteringand applies filters before sort/paginate for lazy large tabs.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| components/layout/DataControlHeader.tsx | Adds withQuickSearchHeaders/QuickSearchHeader, onApplyFilterModelRegistry, pub/sub registry, and reworks toolbar editor to use a state-mirrored committed filter snapshot. |
| components/ui/ReactionKnockoutsDialog.tsx | Wires useToolbarGridFiltering + withQuickSearchHeaders and controlled filterModel on the dialog grid. |
| components/build-model/RastGenomesTable.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| components/build-model/PatricGenomesTable.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/model/[...path]/page.tsx | Extracts ModelTabDataGrid with per-tab useToolbarGridFiltering, filters before sort/paginate, wraps tab columns with withQuickSearchHeaders. |
| app/genome/[...path]/page.tsx | Adds feature/annotation filtering via useToolbarGridFiltering and column-header quick search. |
| app/gapfill/[...path]/page.tsx | Adds gapfill grid filtering via useToolbarGridFiltering and column-header quick search. |
| app/fba/[...path]/page.tsx | Adds reactions/exchanges/pathways filtering hooks, controlled filter model, and column-header quick search. |
| app/(user-data)/myMedia/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/(user-data)/my-models/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/(user-data)/my-jobs/page.tsx | Wraps columns with withQuickSearchHeaders. |
| app/(reference-data)/list-media/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/(reference-data)/genomes/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/(reference-data)/genomes/Annotations/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/(reference-data)/biochem/reactions/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
| app/(reference-data)/biochem/compounds/page.tsx | Wraps columns with withQuickSearchHeaders; removes disableColumnMenu. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| columns={ | ||
| tab.key === 'reactions' | ||
| ? reactionColumns | ||
| : tab.key === 'compounds' | ||
| ? compoundColumns | ||
| : tab.key === 'fba' | ||
| ? fbaColumns | ||
| : tab.key === 'pathways' | ||
| ? pathwayColumns | ||
| : tableConfig[tab.key].columns | ||
| } |
| const [, forceTick] = useState(0); | ||
| const [committed, setCommitted] = useState<{ items: GridFilterItem[]; logicOperator: GridLogicOperator } | undefined>(); | ||
| useEffect(() => { | ||
| setCommitted(committedFilterRegistry.get(apiRef.current)); | ||
| return subscribeCommittedFilter(apiRef.current, () => { | ||
| setCommitted(committedFilterRegistry.get(apiRef.current)); | ||
| forceTick((n) => n + 1); |
… and toolbar sync
…er-column filters Removes the 300ms keystroke debounce in ToolbarSearchField that raced with the multi-column-filter state cascade on pages re-rendering frequently (e.g. my-models polls tracked-job status every 15s), which dropped keystrokes after the first letter when two per-column quick filters were already committed. Now mirrors the per-column QuickSearchHeader contract: typing is purely local state, Enter commits, Escape reverts or clears, and the CSS Highlight effect is driven by the committed term so highlights appear only on Enter. Other committed column filters are preserved (applySearch still reads them from the shared registry). E2E specs updated to press Enter after fill on the global search input. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the prevCommitted ref + useEffect setState pattern (flagged by react-hooks/set-state-in-effect) with React's recommended "adjust state when a prop changes" approach: store prevCommitted in useState and update both prevCommitted and draft synchronously during render when the grid's committed quick-filter value diverges. Functionally equivalent — same divergence guard (only overwrite draft when the user hasn't typed past the previously committed value) — and removes the now-unused useRef import. Unblocks CI on staging. https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the per-column magnifying-glass single-input popover with a 3-field form mirroring one row of the Filter & Columns editor: locked column, operator dropdown (defaulting to the type's quick-search operator — `contains` for strings), and value input matched to the operator. Apply/Enter commits, Cancel/Escape discards, Clear removes this column's quick item. Re-opening on an already-filtered column pre-seeds operator and value from the registry. Filter writes still flow through the shared committed-filter registry and the page's onApplyFilterModel handler, so the data control header remains the single owner of multi-row + AND/OR state. Also suppress MUI's 3-dot column menu on every column the helper sees, so each header is just `Name + magnifying-glass icon`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lumn filtering Reactions Transport column was a JS boolean rendered as Yes/No but declared as the implicit string type, so the quick filter built `is_transport:*No*` in Solr and never matched. Declaring `type: 'boolean'` routes the per-column quick filter through the boolean operator list (is / is not / isEmpty / isNotEmpty) with a Yes/No dropdown that emits true/false — matching how Solr indexes the field. Boolean Value selects in both the toolbar editor and the per-column popover now show Yes/No labels (still emit true/false on the wire). Add a `between` operator for number and date columns. Dates default to it on opening the quick filter, with two From/To pickers side-by-side; either side may be left empty for an open-ended bound. The toolbar editor accepts the same operator via `from, to` text input. Values are stored as a 2-element array preserving empty positions, then routed through: - Server: `buildFilterClause` emits Solr `field:[from TO to]` with `*` sentinels for empty sides. - Client: `matchesFilterItem` compares numerically when bounds + field parse as numbers, else by date, else lexically. Toolbar editor and per-column popover both delegate value coercion to a shared `coerceFilterValue` so they always produce identical GridFilterItem shapes for the same operator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pens `autoFocus` on the TextField wasn't winning against MUI Popover's FocusTrap, which latched onto the Operator select and forced an extra click before typing. Focus the value field (or the From field for `between`) explicitly via the Popover's TransitionProps.onEntered, and select any pre-seeded text so typing replaces it immediately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fy 3-filter e2e test
The icon used to sit immediately to the right of short header text in wide columns. Root cause: the renderHeader Box was sized to fit content, so the flex layout had no extra space for the icon to drift into. Make the Box `flex: 1, width: 100%` so it spans MUI's title-content slot, and push the icon to the far right with a spacer wrapper carrying `ml: auto, pl: 1`. The spacer is a separate Box rather than padding on the Badge — padding on the Badge would shift its absolutely-positioned dot indicator off the icon corner. Sort still triggers on clicks anywhere in the header except the icon: the name text doesn't stopPropagation, so MUI's column-header click handler receives the bubbled event; the icon's openPopover handler stops it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This pull request enhances the filtering and search experience across multiple data grid pages in the application by introducing the
withQuickSearchHeadersutility and integrating theuseToolbarGridFilteringhook where appropriate. These changes ensure a more consistent and powerful quick search/filtering capability in all major data tables. Additionally, some redundant props are removed for cleaner code.Quick search and filtering improvements:
columnswithwithQuickSearchHeaders(columns)in all major data grid pages (e.g., compounds, reactions, genomes, media, jobs, models, user media, and FBA results), enabling consistent quick search functionality in column headers. [1] [2] [3] [4] [5] [6] [7] [8] app/fba/[...path]/page.tsxL831-R862, app/fba/[...path]/page.tsxL850-R889)withQuickSearchHeadersalongsideDataControlHeaderin all relevant files to support the above integration. [1] [2] [3] [4] [5] [6] [7] [8] app/fba/[...path]/page.tsxL34-R35)app/fba/[...path]/page.tsx), addsuseToolbarGridFilteringfor each data grid (reactions, exchanges, pathway maps) and wires up the filter model, filter mode, and toolbar filter handlers for server-side filtering and improved UX. (app/fba/[...path]/page.tsxR604-R616, app/fba/[...path]/page.tsxL831-R862, app/fba/[...path]/page.tsxL850-R889)Code cleanup and simplification:
disableColumnMenuprop from all affectedDataGridcomponents, simplifying the grid configuration and relying on the new quick search headers for column-level actions. [1] [2] [3] [4] [5] [6] [7]These improvements provide a more unified and user-friendly filtering/search experience across the app's data tables.