Skip to content

FEAT: Add dataset catalog navigation and cards - #3071

Open
Julio César Suástegui (juliosuas) wants to merge 3 commits into
microsoft:mainfrom
juliosuas:feat/2747-datasets-catalog
Open

Julio César Suástegui (juliosuas) wants to merge 3 commits into
microsoft:mainfrom
juliosuas:feat/2747-datasets-catalog

Conversation

@juliosuas

@juliosuas Julio César Suástegui (juliosuas) commented Oct 10, 2026 •

Copy link
Copy Markdown

Description

Closes #2747

Summary

Adds a Datasets entry and a catalog of datasets already loaded in memory. Cards come only from GET /api/datasets?loaded_only=true. Search, modality, harm category, the unlabeled facet, and sort stay in the query string, so Back, Forward, and refresh keep them.

A card links to /datasets/detail?selection_key= through the shared query builder in routeParams.ts. The key stays opaque and is used only to find a row in that same catalog response. An unknown key shows a not-found state, truncated to 80 characters, and is rendered as text. This page does not list individual examples; the prompt table stays with #2745.

Sort options are name A–Z, name Z–A, most examples, and fewest examples. Changing the runtime generation drops the catalog on screen and aborts the in-flight request.

Deferred

Provider-only cards and an available-to-load view are deferred. loaded and provider_available remain on DatasetInfo because the API returns them.

Screenshots

Captured from commit abf4f01a41d6a224b7fd79df8c49bc469b17da1d, using the production frontend build and the real backend catalog API with isolated, synthetic in-memory seed data. The dataset names and counts below are demonstration fixtures, not newly bundled datasets. No provider downloads or model calls were needed.

Loaded dataset catalog and navigation

Six loaded datasets, including separate unnamed and literally named __unnamed__ populations. Cards show logical examples, seed pieces, objectives, stored modalities, and harm categories.

Loaded dataset catalog with summary cards and the Datasets navigation entry

Search, facets, and sorting

A filtered catalog with name search, modality, harm category, the unlabeled facet, and most-examples sorting. These selections are represented in the URL.

Dataset catalog filtered by name, modality, harm category, unlabeled status, and sort order

Dataset selection route

Opening a card uses the opaque selection_key query parameter. The route shows the selected catalog summary and explicitly leaves individual examples to the later prompt-table milestone.

Dataset selection route showing summary metadata without an individual prompt table

Empty memory

The catalog stays empty when no datasets are loaded, without exposing provider-loading controls.

Empty dataset catalog with the No datasets in memory message

Mobile selection view in dark mode

The selection route at a 390 × 844 CSS-pixel viewport, with the three counts stacked vertically.

Mobile dark-mode dataset selection showing stacked counts and modality and harm-category badges

Tests and Documentation

Testing

The original validation commands are retained below, with review-time results distinguished from the original report:

  • npm run lint: passed during this review.
  • npm run type-check: passed during this review.
  • npm run test:coverage (109 suites, 2812 tests): reported in the original description; the full coverage run was not repeated during this review.
  • PYRIT_PYTHON=python3 npm run build: retained from the original description; this Linux command was not run verbatim on Windows. The Windows production build below passed.

The changed test suites were run from frontend:

npm test -- --runInBand --silent --runTestsByPath src\App.test.tsx src\components\Datasets\DatasetCatalog.test.tsx src\components\Datasets\DatasetSelection.test.tsx src\components\Datasets\datasetQuery.test.ts src\components\Sidebar\Navigation.test.tsx src\hooks\useLoadedDatasets.test.tsx src\services\api.test.ts src\utils\routeParams.test.ts

Result: 8 suites passed, 230 tests passed.

Production build, with the worktree's Python environment selected from the repository root:

$env:PYRIT_PYTHON = (Resolve-Path '.venv\Scripts\python.exe').Path
Set-Location frontend
npm run build

Result: passed. Vite emitted non-failing configuration and large-chunk warnings.

Browser checks used an isolated Chromium instance against the production build and real in-memory backend. Confirmed empty-memory rendering, catalog filters, refresh and browser Back/Forward preservation, distinct unnamed selections, special-character selection keys, and catalog-only requests without seed-page prefetch. The screenshots above document the additions; they are not a claim that all review findings are resolved.

No repository documentation or notebook files were changed. JupyText was not run for this frontend-only change.

@juliosuas

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

<Text size={400}>No datasets in memory</Text>
<Text size={200}>
Datasets show up here after they are loaded into memory. This view does not download them.
</Text>

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.

Could we keep the result status mounted when the filters match no datasets? Typing an unmatched name removes the only role="status" element and replaces it with plain text. Focus remains in the search box, so the zero-result message is not exposed as a live status update for screen readers.

For example, the transition from 6 datasets to No datasets match "does-not-exist" leaves this whole section with no live region. Keep a status element outside these branches and update it for zero results too. A test covering positive results, zero results, and clearing the search would protect this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. One status region now stays mounted and switches between the count and the no-match message. Added a test for results, zero results, and clearing the search.

return (
<div className={styles.badges}>
{values.map((value, index) => (
<Badge key={`${index}:${value}`} appearance="outline">{value}</Badge>

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.

Could long stored labels wrap inside the badge? A valid category such as cybersecurity_credentials_and_privileged_information_disclosure renders as one 350px-wide line inside a 321px card. Its text spills out on both sides and into the neighboring card. On a 390px mobile viewport it overflows the 306px card by about 22px on each side.

flexWrap moves whole badges but does not wrap an individual label, and the badge still has a fixed 20px height. Please allow long labels to wrap and grow, or provide an accessible truncation treatment. A browser case with a long unbroken harm category would catch this; the current tests only exercise short labels.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. Labels wrap inside the badge and the badge grows. Added a browser test with that long category at card width and at 390px.


return (
<section className={styles.root} aria-labelledby="dataset-selection-title">
<Link className={styles.backLink} to={DATASETS_PATH}>Back to datasets</Link>

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.

Could we preserve the catalog URL when following this link? For example, open a card from /datasets?q=policy&modality=text&sort=logical_examples_desc, then click Back to datasets. This link always goes to /datasets, so it clears the search, selected facets, and sort. Browser Back preserves them, but the page's own Back link does not.

Please keep the originating catalog URL in navigation state and use it here, with /datasets as the fallback for a direct detail link. A test that follows the actual Back link from a filtered catalog would cover this; the current URL-state test only calls navigate(-1).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. The card passes the catalog URL in navigation state and Back uses it, falling back to /datasets for direct links or anything that isn't a /datasets path. Test follows the real link from a filtered catalog.

<DatasetCard dataset={dataset} linked={false} />
</div>
<p className={styles.note}>
Individual examples are not listed in this view.

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.

Could we make it clear that this is temporary, rather than a permanent limitation of this view?

Suggested change
Individual examples are not listed in this view.
Individual examples are not yet listed in this view.
We are still building this experience.

Please update the card's screen-reader description to match, and adjust the text assertions. The actual prompt table can stay deferred to #2745.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied your wording, and the card's screen-reader text matches.

[NARROW_VIEWPORT_QUERY]: {
minWidth: 0,
flex: '1 1 12rem',
},

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.

Could these filter controls use mobileTouchTargetHeight too? With a 390px viewport and pointer: coarse, I measured Modalities, Harm categories, and Sort datasets at 32px high. Search and Refresh correctly grow to 44px.

The options inside SearchableMultiCombobox get the touch helper, but the closed control does not. Applying the helper to filterControl would make these new controls meet the repository's 44px touch minimum. Please include the closed filters in a touch-target browser test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. filterControl uses mobileTouchTargetHeight now, and the touch-target test covers the three closed filters.

Show datasets loaded in memory as cards from GET /api/datasets, with
name search, facets, and sort kept in the query string. Selection uses
the shared query builder for /datasets/detail?selection_key= so unnamed
and special-character keys round-trip. The prompt table stays with microsoft#2745.

Provider-only cards and an available-to-load view are deferred. A runtime
generation change drops the previous catalog and aborts the in-flight load.
@cursor
cursor Bot force-pushed the feat/2747-datasets-catalog branch from abf4f01 to 03ee3d8 Compare October 11, 2026 12:50

This branch has not been deployed

No deployments
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.

FEAT GUI: Add dataset navigation and cards

2 participants