Skip to content

[DT-3313] Add filters to Data Library asset tabs that had none or few - #3926

Open
kevinmarete wants to merge 11 commits into
developfrom
lawson-dt-3313-library-filters-all-asset-type
Open

kevinmarete wants to merge 11 commits into
developfrom
lawson-dt-3313-library-filters-all-asset-type

Conversation

@kevinmarete

@kevinmarete kevinmarete commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Addresses

DT-3313

Security risk: no

Summary

Several Data Library tabs shipped with zero or minimal filtering. AI Models had none; Intellectual Property, Workspaces, Presentations, Publications and Funding Resources had one or two each. This brings every tab up to parity with Studies/Datasets/Clinical Trials/Biospecimens using fields already in the index.

Added — AI Models: Format, License, Cloud, Tags. Intellectual Property: Type, Status. Workspaces: Cloud, Access. Presentations: Event, Format, Access, Presentation Date. Publications: Journal, Access, Published Date. Funding Resources: Funder Name. Each new filter follows the five steps in .github/copilot-instructions.md: FilterState/AvailableFilters, clause + section behaviour in filterRegistry.ts, per-AssetType visibility in dataLibraryFilterConfig.ts, URL parse/serialize, and tests. Cloud and Access grid columns were added so every filter has a visible counterpart.

Removed — the "Datasets Cited (Presentations)?" and "Datasets Cited (Publications)?" booleans. Neither grid exposed a citation column, so dropping the filters would have left the field with no user-facing representation at all; both grids now carry a Datasets Cited column instead, sorting on the rendered Yes/No and showing the citation text in a tooltip where the index has it.

Also fixed — filter option lists were derived from the active tab's current page, and from a corpus already narrowed by the filter being offered. The second half meant checking one value dropped every sibling option, so a multi-select checkbox group could never hold more than one value. Options are now derived from the full corpus with the asset's own filters cleared; filters set on other tabs still apply. parseFiltersFromUrl builds FilterState entirely from the param configs and casts, so a key missing from them parsed as undefined and crashed the first filters[key].length read — it is now seeded from EMPTY_FILTERS.

AI Models, previously unfilterable, with two formats selected at once and the new Cloud column:

AI Models tab with Format, License, Cloud and Tags filters, ONNX and PyTorch both selected

The Cloud columns added to AI Models and Workspaces were byte-identical, as were the Access columns added to Presentations and Publications. Both shapes already existed in the file they were copied into, so chipListColumn and truncatedTextColumn in columns/sharedColumns.tsx now back all seven call sites, rendering unchanged. That took Sonar's duplication on new code from 7.9% to 2.5%; what remains is the buildQuery boilerplate every asset definition shares and the render-a-cell harness every column spec shares, both of which predate this PR and span files it does not otherwise touch.

Two things a reviewer may want to look at directly. develop is merged in (57 commits); the date-range rework that landed there now covers the new Presentation Date and Published Date ranges, and resolving that merge surfaced the Intellectual Property and Funding Resources row predicates skipping their new Type/Status/Funder Name checks whenever no date range was set. Separately, every asset filter's study.assets.* field path is now pinned by a test — a wrong path builds a valid query that quietly matches nothing, and neither the typechecker nor a row-level test catches it.

Lint and type-check clean; full suite green (4,682 tests).

🤖 Generated with Claude Code

“Jonathan and others added 5 commits August 18, 2026 01:57
Resolve conflicts with the date-range work that landed on develop: register
presentationDate and publicationPublishedDate in DATE_RANGE_BOUNDS and
DATE_SECTION_CONFIG so the new ranges get the same inverted-bounds handling,
and gate their clauses and row predicates on isFilterActive.

The auto-merge left the IP and funding row predicates returning early when
their date range was inactive, which skipped the ipType/ipStatus and
funderName checks added on this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Derive each asset's filter options with that asset's own filters cleared.
They were derived from a corpus already narrowed by the filter being offered,
so checking one value dropped every sibling option and a multi-select
checkbox group could never hold more than one value. Filters set on other
tabs still apply.

Seed parseFiltersFromUrl with EMPTY_FILTERS: the object is built entirely
from the param configs and cast to FilterState, so a key missing from them
parsed as undefined and crashed the first filters[key].length read. A
round-trip test now covers every key so an unregistered one fails loudly
rather than silently dropping out of the URL.

Pin the study.assets.* path of every asset filter clause. A wrong path
builds a valid query that matches nothing, which neither the typechecker nor
a row-level test can catch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for DUOS Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 90.43% 12123 / 13406
🔵 Statements 89.91% 12935 / 14386
🔵 Functions 87.4% 3587 / 4104
🔵 Branches 83.06% 7666 / 9229
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/components/data_library/LibraryFilters.tsx 90.72% 90.74% 81.81% 90.42% 255, 276-283, 320, 391, 419-434, 511-515
src/components/data_library/filterRegistry.ts 88.83% 87.79% 97.05% 89% 219, 295, 361-368, 541-548, 607-611, 631-645, 796-810, 862, 898
src/components/data_library/assets/fundingResourceAsset.ts 100% 91.66% 100% 100%
src/components/data_library/assets/intellectualPropertyAsset.ts 100% 92.59% 100% 100%
src/components/data_library/assets/modelAsset.ts 100% 89.28% 100% 100%
src/components/data_library/assets/presentationAsset.ts 100% 90% 100% 100%
src/components/data_library/assets/publicationAsset.ts 100% 91.17% 100% 100%
src/components/data_library/assets/workspaceAsset.ts 100% 85.71% 100% 100%
src/components/data_library/columns/modelColumns.tsx 94.11% 80% 90% 93.75% 94
src/components/data_library/columns/presentationColumns.tsx 100% 95.83% 100% 100%
src/components/data_library/columns/publicationColumns.tsx 100% 92.3% 100% 100%
src/components/data_library/columns/sharedColumns.tsx 95% 100% 88.88% 95% 23
src/components/data_library/columns/workspaceColumns.tsx 94.73% 75% 90.9% 94.11% 116
src/hooks/useLibraryPageState.ts 86.79% 78.57% 80.7% 86.27% 237, 240, 252, 254, 260-261, 265-267, 291, 306, 326-330
src/hooks/useLibraryTabCounts.ts 100% 100% 100% 100%
src/hooks/useLibraryUrlState.ts 98.61% 97.87% 97.22% 99.25% 267, 354
src/libs/dataLibraryFilterConfig.ts 100% 100% 100% 100%
src/types/library.ts 100% 100% 100% 100%
Generated in workflow #7112 for commit 1436a9e by the Vitest Coverage Report Action

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved moderate findings affect date filtering, filter-option derivation, and citation-data access.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds configuration-driven filters, URL persistence, row filtering, and supporting columns across under-filtered Data Library tabs.

Changes:

  • Adds asset-specific filters for models, workspaces, presentations, publications, intellectual property, and funding resources.
  • Adds date-range handling, full-corpus filter options, and Cloud/Access columns.
  • Expands test coverage and removes citation filters.
File summaries
File Description
test/pages/DataLibrary.spec.tsx Updates nested presentation filtering coverage.
test/hooks/useLibraryUrlState.spec.tsx Tests filter URL parsing and round-tripping.
test/hooks/useLibraryPageState.spec.tsx Tests dynamic option derivation.
test/components/data_library/LibraryFilters.spec.tsx Updates visible filter assertions.
test/components/data_library/filterRegistry.spec.ts Tests filter definitions and query paths.
test/components/data_library/columns/workspaceColumns.spec.tsx Tests the workspace Cloud column.
test/components/data_library/columns/publicationColumns.spec.tsx Tests the publication Access column.
test/components/data_library/columns/presentationColumns.spec.tsx Tests the presentation Access column.
test/components/data_library/columns/modelColumns.spec.tsx Tests the model Cloud column.
test/components/data_library/assets/workspaceAsset.spec.ts Tests workspace filtering and Cloud mapping.
test/components/data_library/assets/publicationAsset.spec.ts Tests publication filters and Access mapping.
test/components/data_library/assets/presentationAsset.spec.ts Tests presentation filters and Access mapping.
test/components/data_library/assets/modelAsset.spec.ts Tests model filters and Cloud mapping.
test/components/data_library/assets/intellectualPropertyAsset.spec.ts Tests intellectual property filters.
test/components/data_library/assets/fundingResourceAsset.spec.ts Tests funder-name filtering.
src/types/library.ts Adds filter state and available-filter types.
src/libs/dataLibraryFilterConfig.ts Configures visible filters by asset type.
src/hooks/useLibraryUrlState.ts Adds filter URL parsing and serialization.
src/hooks/useLibraryPageState.ts Derives dynamic filter options.
src/components/data_library/LibraryFilters.tsx Renders new filter controls.
src/components/data_library/filterRegistry.ts Defines filter controls and query clauses.
src/components/data_library/columns/workspaceColumns.tsx Adds the workspace Cloud column.
src/components/data_library/columns/publicationColumns.tsx Adds the publication Access column.
src/components/data_library/columns/presentationColumns.tsx Adds the presentation Access column.
src/components/data_library/columns/modelColumns.tsx Adds the model Cloud column.
src/components/data_library/assets/workspaceAsset.ts Maps and filters workspace fields.
src/components/data_library/assets/publicationAsset.ts Applies publication predicates.
src/components/data_library/assets/presentationAsset.ts Applies presentation predicates.
src/components/data_library/assets/modelAsset.ts Maps and filters model fields.
src/components/data_library/assets/intellectualPropertyAsset.ts Applies intellectual property predicates.
src/components/data_library/assets/fundingResourceAsset.ts Applies funding-resource predicates.
Review details

Suppressed comments (1)

src/libs/dataLibraryFilterConfig.ts:48

  • These replacements remove the only Data Library controls for the existing citation values, while the Presentation and Publication grid columns still do not render that field. After this change users can no longer inspect or filter citation data in either tab. Please retain the old filters or add a visible Citation column before dropping them.
    visibleFilters: ['publicationJournal', 'publicationAccess', 'publicationPublishedDate'],
  },
  [AssetType.PRESENTATIONS]: {
    visibleFilters: ['presentationEvent', 'presentationFormat', 'presentationAccess', 'presentationDate'],
  • Files reviewed: 31/31 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/components/data_library/assets/presentationAsset.ts Outdated
Comment thread src/components/data_library/assets/publicationAsset.ts Outdated
Comment thread src/hooks/useLibraryPageState.ts Outdated
kevinmarete and others added 2 commits September 14, 2026 16:03
The Cloud columns added to AI Models and Workspaces were byte-identical, as
were the Access columns added to Presentations and Publications, which is
most of the duplication Sonar reports on this PR. Both shapes already
existed: the model/workspace Tags columns are the same chip list, and
Workspaces already carried the same Access column.

One chipListColumn and one truncatedTextColumn now back all seven call
sites. Rendering is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Derive filter options from a corpus narrowed only by the search term. Every
active filter is applied to the counts query at the Elasticsearch level and
those clauses match whole studies, so clearing an asset's filters after the
response came back could not recover a value whose only study the filter had
already removed. When nothing is filtered this resolves to the same query key
as the counts query, so it shares that request. The previous regression test
could not catch this: both models lived in one study.

Treat a missing date as matching neither bound in the presentation and
publication predicates, mirroring the ES range clause, which never matches a
document without the field, and the existing funding predicate. Intellectual
Property carried the same bug from before this branch and is fixed with them,
so all three read the same way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Filter-option derivation clears all active filters, so cross-tab filtering is not preserved.

Review details

Suppressed comments (1)

src/hooks/useLibraryPageState.ts:124

  • This query passes EMPTY_FILTERS, so it also removes filters owned by other tabs. For example, with accessManagement=['controlled'] active, model options are populated from studies outside the controlled scope, even though the surrounding state handling says filters combine across tabs. Build each asset's option corpus with only that asset's own filter keys cleared while retaining the other active filters (or use facet aggregations); a single all-empty corpus does not preserve the stated cross-tab behavior.
  const { data: optionCorpusResponse } = useLibraryTabCounts(
    libraryConfig,
    EMPTY_FILTERS,
    urlState.query ?? '',
  )
  • Files reviewed: 32/32 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Deriving options from an entirely unfiltered corpus dropped the filters other
tabs had set, so a tab could offer values its own grid excludes. Each asset
now gets a corpus with only its own filter keys cleared, via useQueries.

Filter sets that come out identical share one cache entry, so this is a single
request with nothing filtered — the same one the counts query makes — plus one
per asset that actually owns an active filter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Two moderate findings remain in useLibraryPageState.ts regarding selected options and option-query loading/error handling.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

src/hooks/useLibraryPageState.ts:128

  • The new option-corpus queries are not included in the loading state exposed by this hook. LibraryPageShell still passes only isMetadataLoading to LibraryFilters, so when these queries are pending after a filter/search change, the new sections can receive empty availableFilters and render “No filters available” instead of a loading or previous state; query failures are indistinguishable from a corpus with no values. Expose/handle the option-query loading/error state or preserve the previous options while refetching.
  const optionCorpusResults = useLibraryTabCountsFor(libraryConfig, optionCorpusFilterSets, urlState.query ?? '')
  • Files reviewed: 33/33 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/hooks/useLibraryPageState.ts
A value selected on the current tab can be absent from the corpus its options
are derived from, because a filter owned by another tab excludes every study
carrying it. The checkbox then disappeared while the filter stayed active, and
the external chips deliberately skip keys the tab renders itself, so nothing
was left to clear it with. Selected values are now always present in their own
section's options.

useQueries starts a new query key empty rather than carrying the previous
answer, so a filter set on another tab blanked every section to "No filters
available" for a tick. Fall back to the counts response, which is already
loaded and narrower only by this asset's own filters. Toggling a filter on the
tab being viewed does not change that tab's corpus key at all, so the usual
interaction never refetches these in the first place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Four moderate findings remain unresolved, covering option-corpus query fan-out and citation URL/control regressions.

Review details

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

src/hooks/useLibraryTabCounts.ts:95

  • This creates one full STUDIES_AGG/top_hits query per distinct asset filter set. With filters persisted across several tabs, a single filter edit can fan out into up to eight option-corpus requests plus the counts request, each materializing up to 10,000 study documents; that will scale poorly for the Data Library. Prefer facet aggregations, batching, or another bounded strategy before making this the default path.
    src/hooks/useLibraryUrlState.ts:296
  • Removing the citation serializers also removes the only code that deleted their old URL aliases. A user opening an existing ?datasetsCited=..., ?presentationsDatasetsCited=..., or ?publicationsDatasetsCited=... URL and then clearing or changing filters will keep those unsupported parameters in the URL forever, because updateState copies the current search params before serialization. Delete these legacy parameters even though they are no longer parsed.

src/hooks/useLibraryPageState.ts:128

  • This starts one full STUDIES_AGG option-corpus query per distinct filter set. Once filters are active on several asset tabs, a single checkbox change can create up to eight new size: 0 requests in addition to the main counts request, and each aggregation returns top_hits with study.* for up to 10,000 studies. That creates an N+1/full-corpus fan-out as filters persist across tabs; please consolidate this into one facet query or lazily fetch only the option corpus needed for the current view.
  const optionCorpusResults = useLibraryTabCountsFor(libraryConfig, optionCorpusFilterSets, urlState.query ?? '')

src/libs/dataLibraryFilterConfig.ts:48

  • These replacements remove the existing datasetsCited/publicationsDatasetsCited controls, while the Presentation and Publication grids still expose no citation column. That makes the indexed citation state unreachable in the Data Library and is a user-visible regression; please either retain the filters or add a Citation column before removing them.
    visibleFilters: ['publicationJournal', 'publicationAccess', 'publicationPublishedDate'],
  },
  [AssetType.PRESENTATIONS]: {
    visibleFilters: ['presentationEvent', 'presentationFormat', 'presentationAccess', 'presentationDate'],
  • Files reviewed: 33/33 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Only the visible tab renders a filter panel, so only its corpus is fetched
rather than one per asset. Every other asset reads the counts response, which
is already loaded; their options only label external chips, which fall back to
the raw value. Clearing the tab's own keys is a no-op until it actually has a
filter set, so this shares the counts query's request until then — at most one
extra request, where the fan-out could previously reach eight.

Serialization also deletes datasetsCited, presentationsDatasetsCited and
publicationsDatasetsCited. Dropping their serializers took away the only code
that removed them, and updateState serializes over a copy of the current
params, so an old link's parameters would have ridden along forever.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Removing the citation filters leaves the existing citation field without a user-facing representation.

Review details

Suppressed comments (2)

src/components/data_library/filterRegistry.ts:99

  • Removing the citation filter definitions also removes the only rendered path for the existing citation data: both asset transforms still populate citation, but neither the Presentation nor Publication columns exposes it. Users following existing Datasets Cited links will therefore lose any way to see this field; please retain the filters or add a Citation column before removing them.
const BOOL_FILTER_KEYS: Array<'instantApproval'> = [
  'instantApproval',
]

src/libs/dataLibraryFilterConfig.ts:48

  • Removing these two existing filters leaves citation with no user-facing representation: neither the Publications nor Presentations column definitions exposes it, while both transforms still carry the field. This regresses the only way users could identify cited/non-cited assets; please retain the filters or add Citation columns before removing them.
    visibleFilters: ['publicationJournal', 'publicationAccess', 'publicationPublishedDate'],
  },
  [AssetType.PRESENTATIONS]: {
    visibleFilters: ['presentationEvent', 'presentationFormat', 'presentationAccess', 'presentationDate'],
  • Files reviewed: 33/33 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

The citation booleans were removed as filters while no grid column exposed
the field, so the indexed citation state had no user-facing representation at
all. Both grids now carry it, sorting and filtering on the rendered Yes/No and
showing the citation text in a tooltip where the index has it.

A missing field reads as No, matching how both transforms default it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@kevinmarete
kevinmarete requested a lite review from Copilot September 14, 2026 21:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The requested filters, state handling, columns, row filtering, and test coverage are complete with no unresolved blocking issues.

Review details
  • Files reviewed: 33/33 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@kevinmarete
kevinmarete marked this pull request as ready for review September 14, 2026 21:47
@kevinmarete
kevinmarete requested a review from a team as a code owner September 14, 2026 21:47
@kevinmarete
kevinmarete requested review from fboulnois and otchet-broad and removed request for a team September 14, 2026 21:47
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