Fix biochem search reliability and harden DataControlHeader behavior#177
Merged
VibhavSetlur merged 8 commits intoModelSEED:stagingfrom May 4, 2026
Merged
Fix biochem search reliability and harden DataControlHeader behavior#177VibhavSetlur merged 8 commits intoModelSEED:stagingfrom
VibhavSetlur merged 8 commits intoModelSEED:stagingfrom
Conversation
…eld) Solr compounds_staging does not define an ontology query field; quick search included ontology and Solr returned 400, which broke toolbar search and looked like an infinite load. - Remove ontology from compound search field list and Solr fl list - Mark ontology column filterable: false (no Solr field to filter on) - Surface Solr error.detail in fetch failures for easier debugging - Add unit test asserting quick-search q omits ontology - Add Playwright cases for cpd05323 and lowercase glucoiberin Co-authored-by: Cursor <cursoragent@cursor.com>
…nd refinement REST biochem endpoints omit Solr offsets; widen fetch (capped), filter quick matches across searchable fields locally, slice for pagination. - Mirror Solr quick-search OR-of-fields semantics (case folding, short-token prefix) - Default searchFields via CPD_/RXN_ constants inside fetchModelseedApiBiochem - Merge Solr defaults through getCompoundsFromModelseedApi/getReactionsFromModelseedApi - Skip ontology compound column filters server-side REST (Solr has no ontology) - Resolve synonyms column sorting to aliases for REST local sortRest - Add unit coverage for widen-fetch, ontology noop, multi-field refine Co-authored-by: Cursor <cursoragent@cursor.com>
- Toolbar search: derive display from grid quick filter + draft (no stale input) - Placeholders: use real App Router paths (/genomes, /list-media, gapfill, fba, genome) - Single pagination: hideFooter on genome, gapfill, FBA, my-jobs, knockouts dialog - PATRIC: when column filters are active, batch-fetch then filterDocsByGridModel + local sort/slice - Export filterDocsByGridModel and sortGridDocsLocally; normalize Date in local filters - Docs: docs/DATA_CONTROL_HEADER.md integration map; extend E2E smoke (Annotations, my-jobs) Co-authored-by: Cursor <cursoragent@cursor.com>
… key stability - Fix date operators (after/before/onOrAfter/onOrBefore) in matchesFilterItem for non-numeric fields (date strings were falling through to string comparison) - Skip ontology field filter items in buildSolrUrl for compounds (Solr compounds_staging has no ontology field, caused 400 errors) - Stabilize PatricGenomesTable query key: JSON.stringify filter items to avoid new-reference re-fetches; only include pagination for server mode - Fix pre-existing type errors in PatricGenomesTable.tsx
…olHeader - Replace single draftRow with draftRows array to support multiple filters - Add AND/OR logic operator selector at top of filter section - Each filter row now has an 'x' button to remove it - Add '+ Add Filter' button to add new filter rows - Update openEditor to load ALL existing filter items (not just first) - Update saveChanges to persist all filled rows with selected logic operator - Logic operator (AND/OR) now saved in filterModel.logicOperator
…lr matching - buildCaseVariants generates lowercase, uppercase, titlecase, and original variants - buildEqualsVariantClause, buildWildcardVariantClause use variants for equals, contains, startsWith, endsWith - Operators updated: equals/is, not/doesNotEqual, doesNotContain, startsWith, endsWith, isAnyOf, contains - Non-alpha values bypass variant generation (numbers, symbols) - Regression test verifies status=ok query contains both status:"ok" and status:"OK"
- Import gridFilteredRowCountSelector from MUI DataGrid - Use filtered count in CustomPagination so pagination shows correct total after client-side filtering (search bar or column filters) - Falls back to total rowCount when filtered count unavailable - Fixes issue where pagination showed 104 total even when filtered results were only 2 pages
Replace blank compound structure tiles with clear "Compound image unavailable" fallbacks in reaction equation cards and compound detail pages, and link subsystem reaction IDs directly to reaction detail routes. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR improves biochem search reliability (Solr + modelseed-api fallback paths) and hardens shared DataControlHeader behavior across multiple grid consumers, while improving UI fallbacks for compounds without structure images and adding/expanding test coverage.
Changes:
- Fix Solr biochem query building (remove invalid compound
ontologysearch/filter usage; add case-variant expansion for case-sensitive fields; improve Solr error detail). - Strengthen grid toolbar behavior (search/filter syncing, filtered pagination totals, multi-filter draft UI) and standardize grids to use toolbar pagination (
hideFooter). - Improve compound structure rendering fallbacks and add unit + e2e coverage for the above regressions.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/api/biochem.test.ts | Adds unit coverage for local filter helper and Solr query shape regressions. |
| tests/unit/api/biochem-rest-filtering.test.ts | Adds unit coverage for modelseed-api client-side quick-search refinement + ontology filter ignoring. |
| tests/e2e/datacontrol-header.spec.ts | Expands cross-page toolbar smoke coverage (placeholder + pagination duplication). |
| tests/e2e/biochem/compounds.spec.ts | Adds e2e assertions for compound ID and case-insensitive name search. |
| lib/api/biochem.ts | Fixes compound Solr query fields, adds case variants, improves REST local filtering/sorting/quick-refine helpers, and improves Solr error reporting. |
| docs/DATA_CONTROL_HEADER.md | Documents where DataControlHeader is used and expected grid modes/semantics. |
| components/ui/ReactionKnockoutsDialog.tsx | Hides DataGrid footer to avoid duplicate pagination when using the toolbar. |
| components/ui/MoleculeRenderer.tsx | Adds explicit “image unavailable” placeholder instead of blank/transparent tiles. |
| components/layout/DataControlHeader.tsx | Improves pagination total under client filtering, keeps quick-search input synced with grid state, and expands filter editor UX. |
| components/build-model/PatricGenomesTable.tsx | Adds client-side batch filtering/sorting for PATRIC when column filters are active. |
| app/genome/[...path]/page.tsx | Hides DataGrid footer to use toolbar pagination consistently. |
| app/gapfill/[...path]/page.tsx | Hides DataGrid footer to use toolbar pagination consistently. |
| app/fba/[...path]/page.tsx | Hides DataGrid footer to use toolbar pagination consistently. |
| app/(user-data)/my-jobs/page.tsx | Hides DataGrid footer to prevent duplicate pagination. |
| app/(reference-data)/genomes/Annotations/page.tsx | Links reaction IDs to reaction detail routes in the annotations grid. |
| app/(reference-data)/biochem/compounds/page.tsx | Marks ontology column non-sortable/non-filterable to reflect Solr schema limitations. |
| app/(reference-data)/biochem/compounds/[id]/page.tsx | Adds a visible placeholder for missing/broken compound images on the detail page. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
59
to
+90
| const { data, isLoading, error } = useQuery({ | ||
| queryKey: [ | ||
| 'patric-genomes', | ||
| query, | ||
| paginationModel.page, | ||
| paginationModel.pageSize, | ||
| columnFiltersActive, | ||
| // Only include pagination in query key for server mode (client-mode paginates in memory) | ||
| ...(columnFiltersActive ? [] : [paginationModel.page, paginationModel.pageSize]), | ||
| sortToken, | ||
| // Stable string key for filter items (avoids new-reference re-fetches) | ||
| JSON.stringify(filterModel.items ?? []), | ||
| ], | ||
| queryFn: () => | ||
| searchPatricGenomes({ | ||
| queryFn: async () => { | ||
| if (columnFiltersActive) { | ||
| const raw = await searchPatricGenomes({ | ||
| query, | ||
| limit: PATRIC_LOCAL_FILTER_BATCH, | ||
| offset: 0, | ||
| sort: sortToken, | ||
| }); | ||
| const filtered = filterDocsByGridModel( | ||
| raw.rows as unknown as Record<string, unknown>[], | ||
| filterModel.items ?? [], | ||
| ) as unknown as PatricGenome[]; | ||
| const sm = sortModel[0]; | ||
| const sorted = sm?.field | ||
| ? sortGridDocsLocally(filtered, { field: sm.field, desc: sm.sort === 'desc' }) | ||
| : filtered; | ||
| const total = sorted.length; | ||
| const start = paginationModel.page * paginationModel.pageSize; | ||
| const rows = sorted.slice(start, start + paginationModel.pageSize) as PatricGenome[]; | ||
| return { rows, total }; | ||
| } |
Comment on lines
+432
to
+435
| const parsed = JSON.parse(text) as { error?: { msg?: string } }; | ||
| detail = parsed?.error?.msg ? `: ${parsed.error.msg}` : (text?.slice(0, 240) ?? ''); | ||
| } catch { | ||
| // ignore JSON parse failures |
Comment on lines
308
to
+417
| @@ -385,13 +386,35 @@ export default function CompoundDetailPage() { | |||
| <Box sx={{ display: 'flex', gap: 3, flexWrap: 'wrap', mb: 3 }}> | |||
| {/* Image */} | |||
| <Box sx={{ width: 220, flexShrink: 0 }}> | |||
| {/* eslint-disable-next-line @next/next/no-img-element */} | |||
| <img | |||
| src={getCompoundImageUrl(cpd.id)} | |||
| alt={`Structure of ${cpd.id}`} | |||
| style={{ maxWidth: '100%', height: 'auto', display: 'block' }} | |||
| onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} | |||
| /> | |||
| {(!cpd.smiles || imageUnavailable) ? ( | |||
| <Box | |||
| aria-label={`Compound image unavailable for ${cpd.id}`} | |||
| sx={{ | |||
| width: '100%', | |||
| minHeight: 220, | |||
| border: '1px dashed #cbd5e1', | |||
| borderRadius: 1, | |||
| bgcolor: '#f8fafc', | |||
| display: 'flex', | |||
| alignItems: 'center', | |||
| justifyContent: 'center', | |||
| textAlign: 'center', | |||
| px: 2, | |||
| }} | |||
| > | |||
| <Typography variant="body2" color="text.secondary"> | |||
| Compound image unavailable | |||
| </Typography> | |||
| </Box> | |||
| ) : ( | |||
| // eslint-disable-next-line @next/next/no-img-element | |||
| <img | |||
| src={getCompoundImageUrl(cpd.id)} | |||
| alt={`Structure of ${cpd.id}`} | |||
| style={{ maxWidth: '100%', height: 'auto', display: 'block' }} | |||
| onError={() => setImageUnavailable(true)} | |||
| /> | |||
| )} | |||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
cpd05323, case-insensitive terms likeglucoiberin) by removing invalidontologyquery clauses and tightening query handling.DataControlHeaderbehavior across grid consumers (search/filter sync, pagination consistency, server/client filter handling on PATRIC and related pages).Test plan
npm run test -- --run tests/unit/api/biochem.test.ts tests/unit/api/biochem-rest-filtering.test.tsnpx eslint components/layout/DataControlHeader.tsx components/build-model/PatricGenomesTable.tsx/biochem/compoundsquick search (cpd05323,glucoiberin) and fallback rendering for compounds without structure data.Made with Cursor