Skip to content

Fix biochem search reliability and harden DataControlHeader behavior#177

Merged
VibhavSetlur merged 8 commits intoModelSEED:stagingfrom
VibhavSetlur:staging
May 4, 2026
Merged

Fix biochem search reliability and harden DataControlHeader behavior#177
VibhavSetlur merged 8 commits intoModelSEED:stagingfrom
VibhavSetlur:staging

Conversation

@VibhavSetlur
Copy link
Copy Markdown
Collaborator

Summary

  • Fix compounds quick search regressions on Solr (cpd05323, case-insensitive terms like glucoiberin) by removing invalid ontology query clauses and tightening query handling.
  • Harden DataControlHeader behavior across grid consumers (search/filter sync, pagination consistency, server/client filter handling on PATRIC and related pages).
  • Improve UI fallbacks for missing compound structures by showing clear unavailable-image placeholders instead of blank tiles, and link subsystem reaction IDs to reaction detail routes.

Test plan

  • npm run test -- --run tests/unit/api/biochem.test.ts tests/unit/api/biochem-rest-filtering.test.ts
  • npx eslint components/layout/DataControlHeader.tsx components/build-model/PatricGenomesTable.tsx
  • Manual verification of /biochem/compounds quick search (cpd05323, glucoiberin) and fallback rendering for compounds without structure data.

Made with Cursor

VibhavSetlur and others added 8 commits May 4, 2026 11:54
…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>
Copilot AI review requested due to automatic review settings May 4, 2026 19:01
@VibhavSetlur VibhavSetlur merged commit 4e72579 into ModelSEED:staging May 4, 2026
5 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ontology search/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 thread lib/api/biochem.ts
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)}
/>
)}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants