Skip to content

feat: refacto search with facets and multiple configs#847

Open
geoffreyaldebert wants to merge 8 commits intomainfrom
refacto-search
Open

feat: refacto search with facets and multiple configs#847
geoffreyaldebert wants to merge 8 commits intomainfrom
refacto-search

Conversation

@geoffreyaldebert
Copy link
Contributor

No description provided.

@geoffreyaldebert
Copy link
Contributor Author

Copy link
Contributor

@maudetes maudetes left a comment

Choose a reason for hiding this comment

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

@geoffreyaldebert is there a small description of the changes (facets + single search, etc?)

@ThibaudDauce ThibaudDauce mentioned this pull request Jan 28, 2026
ThibaudDauce added a commit that referenced this pull request Jan 28, 2026
Extracted from #847

- Created ObjectCard base component with badges, media and default slots
to provide a consistent card layout (border, hover, flex structure,
fr-enlarge-link) shared across all horizontal cards. Created
sub-components ObjectCardBadge (single badge only, uses absolute
positioning), ObjectCardHeader and ObjectCardShortDescription (CSS
line-clamp-2 + JS truncation to limit DOM size).
- Created new card components: TopicCard, PostCard,
DiscussionMessageCard, and ReuseHorizontalCard (extracted from the
horizontal/search mode of ReuseCard into its own component).
- Refactored DatasetCard and DataserviceCard to use ObjectCard. Added
organization logo / avatar / placeholder on the left side of
DataserviceCard, matching the layout of DatasetCard.
- Added consistent iconography across all cards using Remix Icons
(RiDatabase2Line for datasets, RiTerminalLine for dataservices,
RiLineChartLine for reuses, RiChat3Line for discussions, RiArticleLine
for posts, RiBookShelfLine for topics), with aria-hidden="true" on all
decorative icons.
- Extended Placeholder component with Dataservice and Topic types
(RiTerminalLine and RiBookShelfLine).
- Fixed badge layout on DataserviceCard: removed conditional mt-2 that
caused misalignment in grids when a card had badges next to one without.
- Fixed DiscussionCard (components/Discussions/) to guard against null
closed_by with v-else-if.
- Used RiSubtractLine as separator consistently across all cards
(replaced middle dots in TopicCard and DiscussionMessageCard).
- Added removeMarkdown to TopicCard description truncation, consistent
with other cards.
- Updated the /design/cards page to showcase all card types in a
2-column grid, fetching data directly via useAPI.
- Moved types to datagouv-components: Page, Post, Comment, Thread.
- Changed Discussion types to use UserReference/OrganizationReference
instead of full User/Organization. Adapted getOwnerEmails in
utils/owner.ts to fetch user by ID since UserReference doesn't carry
email.

Fix #867


<img width="1678" height="2155" alt="image"
src="https://github.com/user-attachments/assets/40795a85-9891-4163-a307-8357bc34b46a"
/>
ThibaudDauce and others added 2 commits January 29, 2026 12:27
# Conflicts:
#	datagouv-components/src/components/DataserviceCard.vue
#	datagouv-components/src/components/DatasetCard.vue
#	datagouv-components/src/components/PostCard.vue
#	datagouv-components/src/components/TopicCard.vue
#	datagouv-components/src/main.ts
- Remove DiscussionCard.vue (replaced by DiscussionMessageCard.vue in #903)
- Reset ReuseCard.vue to main version

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ThibaudDauce added a commit that referenced this pull request Feb 3, 2026
## Summary

Introduces a unified `GlobalSearch` component in `datagouv-components`
that replaces the separate search pages for datasets, dataservices, and
reuses.

### New components in `datagouv-components`

- `GlobalSearch` - Unified search component with configurable filters
per search type
- Form components: `OrganizationSelect`, `OrganizationTypeSelect`,
`TagSelect`, `FormatSelect`, `LicenseSelect`, `SchemaSelect`,
`GeozoneSelect`, `GranularitySelect`, `BadgeSelect`
- UI components: `RadioGroup`, `RadioInput`, `DoubleFilter`, `Sidemenu`

### New composables

- `useDebouncedRef` - Like VueUse's `refDebounced` but with a `flush()`
method for immediate sync (needed for "reset filters" to apply
instantly)
- `useRouteQueryBoolean` - Boolean handling for route query params
- `useStableQueryParams` - Creates stable query params that only update
when content changes (prevents infinite re-fetch loops)
- `useSelectModelSync` / `useAsyncSelectModelSync` - Bidirectional sync
between model (full object) and id (identifier)

### Other changes

- Switch from `useUrlSearchParams` to `useRouteQuery` from
`@vueuse/router`
- Select components now fetch their own data when given an unknown ID
- Explicit type annotations added to inline callbacks (e.g., `(option: {
text: string }) => option.text`). This is required because moving
`SelectGroup`/`SearchableSelect` to `datagouv-components` changed how
generics are inferred across package boundaries. TypeScript can't infer
the type from the `options` prop when the component comes from an
external package.

### Removed

- `components/Datasets/SearchPage.vue`
- `components/Dataservices/SearchPage.vue`
- `components/Reuses/ListPage.vue`
- `components/Reuses/ListFromOrganization.vue`
- `components/OrganizationSelect.vue`

---

## Breaking changes

### Atom feed link removed from dataset search

The atom feed was an external link to the API
(`/api/1/datasets/recent.atom?...`). Reintroducing it would require
mapping the unified filter state back to API v1 query params, which is
straightforward if needed.

### CSV export button removed from organization dataset pages

The CSV button used a manually constructed URL
(`/api/1/organizations/{slug}/datasets.csv`). With the new
`GlobalSearch` component using `hiddenFilters`, the organization slug is
not available in the component. Reintroducing it would require either
passing the slug as a prop or adding a slot for custom actions.

---

## Anticipated questions

### Why does GlobalSearch make 3 simultaneous API calls?

The component fetches datasets, dataservices, and reuses in parallel to
display result counts on each tab (e.g., "Datasets (1234)"). This is the
same behavior as the current data.gouv.fr site. Requests are optimized
via `useStableQueryParams` which prevents unnecessary re-fetches when
params don't actually change.

### Why does useStableQueryParams use JSON.stringify for comparison?

The params object is built deterministically (keys are always added in
the same order), so JSON.stringify is safe for comparison. Alternatives
like `lodash.isEqual` or `fast-deep-equal` would work but add a
dependency for a simple use case.

### Why switch from useUrlSearchParams to useRouteQuery?

`useUrlSearchParams` (VueUse core) directly manipulates
`window.location` and can cause issues with the Vue router.
`useRouteQuery` (VueUse router) integrates properly with vue-router and
Nuxt.

### Why create useDebouncedRef instead of using refDebounced?

VueUse's `refDebounced` doesn't allow flushing the value immediately. We
need `flush()` so that "Reset filters" applies the change instantly
instead of waiting for the debounce.

### How does hiddenFilters work?

`hiddenFilters` allows pre-setting filters that are sent to the API but
not displayed in the UI. Example: on `/organizations/{oid}/datasets`, we
use:
```ts
hiddenFilters: [{ key: 'organization', value: organization.id }]
```
This scopes the search to the organization without showing the
"Organization" filter in the sidebar.

### What does "Select components fetch their own data" mean?

Previously, if you loaded a page with `?organization=xxx` in the URL,
the parent had to provide the list of organizations to resolve the ID to
a full object. Now, each Select (OrganizationSelect, TagSelect, etc.)
can make an API call to fetch the object corresponding to the ID if it's
not in its options list.

---

## Not yet migrated from #847

The following features from PR #847 are not included in this PR and will
be added later:

- **Additional search types**: organizations, discussions, posts, topics
(this PR only covers datasets, dataservices, reuses)
- **Facet-based filters with counts**: Displaying result counts next to
each filter option (e.g., "Tabulaires (1234)")
- **Additional filter types**:
- `format_family` (tabular, machine_readable, geographical, documents,
other)
  - `access_type` (open, open_with_constraint, restricted)
  - `last_update_range` (last_30_days, last_12_months, last_3_years)
- `producer_type` (public-service, local-authority, company,
association, user)
  - `type` and `topic` for reuses
- **Advanced filters accordion**: Collapsible section with
organization/tags search showing facet counts
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.

3 participants