Skip to content

feat(ui): filter flags by metadata key-value pairs (closes #3739)#5667

Open
hiepau1231 wants to merge 12 commits intoflipt-io:v2from
hiepau1231:feat/filter-flags-by-metadata
Open

feat(ui): filter flags by metadata key-value pairs (closes #3739)#5667
hiepau1231 wants to merge 12 commits intoflipt-io:v2from
hiepau1231:feat/filter-flags-by-metadata

Conversation

@hiepau1231
Copy link
Copy Markdown

@hiepau1231 hiepau1231 commented Apr 4, 2026

Summary

Fixes #3739

Adds client-side metadata filtering to the Flags list page (/namespaces/:ns/flags). Users can now narrow the flag list by one or more metadata key-value pairs without any backend changes.

  • MetadataFilterPopover — new toolbar button (sliders icon) opens a popover with a key input (with datalist autocomplete from existing flag metadata) and a value input. Submitting adds an active filter.
  • Filter chips — active filters render as removable Badge chips below the toolbar, with per-chip × remove and a "Clear all" button.
  • Filter logic — AND semantics, case-insensitive substring match on string-coerced metadata values. Implemented as a useMemo pre-filter (applyMetadataFilters) that runs before TanStack Table sees the data, keeping the change minimal and non-invasive.
  • Empty state — updated condition so metadata-only filtering (no text search) correctly shows "No flags matched your search" instead of the "Create Your First Flag" state.

Changes

File Change
ui/src/types/Flag.ts Add MetadataFilter interface
ui/src/utils/flagMetadataFilter.ts Pure filter utility (new)
ui/src/components/flags/MetadataFilterPopover.tsx New popover component
ui/src/components/flags/FlagTable.tsx State, memos, toolbar button, chips, empty-state
ui/src/utils/flagMetadataFilter.test.ts 8 unit tests for filter logic
ui/src/components/flags/MetadataFilterPopover.test.tsx 5 unit tests for popover
ui/src/components/flags/FlagTable.test.tsx 9 integration tests
ui/babel.config.cjs Add @babel/preset-react for JSX test support
ui/src/setupTests.ts Add jest-dom matchers + TextEncoder polyfill

Test Plan

  • applyMetadataFilters unit tests (8): no filters, single filter, case-insensitive, substring, AND logic, missing key, numeric values, boolean values
  • MetadataFilterPopover unit tests (5): renders trigger, calls onAdd, guards empty key/value, resets after add
  • FlagTable integration tests (9): renders all flags, Filter button, add chip, hide non-matching, remove chip, AND logic, empty state, clear all, combined text+metadata
  • All 43 tests passing, tsc --noEmit clean, eslint clean

hiepau1231 and others added 6 commits April 4, 2026 13:36
Add client-side metadata filter feature design: filter popover + chips
in FlagTable, pre-filter logic with AND semantics, MetadataFilterPopover
component using existing Combobox/Badge/Popover primitives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Task-by-task TDD plan: MetadataFilter type, applyMetadataFilters utility,
MetadataFilterPopover component, FlagTable wiring, smoke test steps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ISSUE-1: Replace Formik Input with BaseInput in MetadataFilterPopover
- ISSUE-2: Add conditional infra files to File Map
- ISSUE-3: Correct Jest config key to setupFilesAfterEnv
- ISSUE-4: Add FlagTable integration tests (8 cases incl. combined filter)
- ISSUE-5: Document filter semantics decision explicitly
- ISSUE-6: Fix FlagTable test mocks to use actual import paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ISSUE-7: Add jest.useFakeTimers()/act(advanceTimersByTime(600)) around
  Searchbox debounce in combined text+metadata test
- ISSUE-8: Wrap FlagTable in MemoryRouter to satisfy useNavigate() context
  requirement from FlagListItem and EmptyFlagList child components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ISSUE-9: Replace ambiguous mockFlags data + search term.
  Renamed to Alpha/Beta/Gamma with distinct keys to avoid 'Flag'/'a'
  substring false-matches. Combined test now uses 'alpha' as search
  term (uniquely matches key='alpha', name='Alpha' only), making
  the combined text+metadata AND assertion unambiguous.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hiepau1231 hiepau1231 requested a review from a team as a code owner April 4, 2026 07:30
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Apr 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 4, 2026

👋 Hi @hiepau1231! Thanks for your contribution to this project.

It looks like one or more of your commits are missing a DCO (Developer Certificate of Origin) sign-off. The DCO is a simple way for you to certify that you have the right to submit this code under the project's license.

How to fix this:

# For future commits, use the -s flag
git commit -s -m "Your commit message"

# To sign off on existing commits in this PR
git rebase HEAD~$(git rev-list --count origin/v2..HEAD) --signoff
git push --force-with-lease

The -s flag adds this line to your commit message:
Signed-off-by: Your Name <your.email@example.com>

📋 View the failing DCO check for more details

For more information about the DCO, visit: https://developercertificate.org/

Signed-off-by: hiepau1231 <hiepau1231@gmail.com>
- Add @testing-library/react, jest-dom, @babel/preset-react for JSX test support
- Configure setupFilesAfterEnv with jest-dom matchers

Signed-off-by: hiepau1231 <hiepau1231@gmail.com>
- Add metadataFilters state, availableMetadataKeys + filteredFlags memos
- Pre-filter flags via applyMetadataFilters before passing to TanStack Table
- Add MetadataFilterPopover in toolbar
- Render active filter chips with per-chip remove and Clear all
- Update empty-state condition to handle metadata-only filters

Signed-off-by: hiepau1231 <hiepau1231@gmail.com>
- 9 tests covering toolbar, chips, AND logic, empty states, combined text+metadata
- Add TextEncoder/TextDecoder polyfill for react-router compatibility in jsdom

Signed-off-by: hiepau1231 <hiepau1231@gmail.com>
Signed-off-by: hiepau1231 <hiepau1231@gmail.com>
@hiepau1231 hiepau1231 force-pushed the feat/filter-flags-by-metadata branch from 99b0c01 to d9ef097 Compare April 4, 2026 07:34
@erka
Copy link
Copy Markdown
Contributor

erka commented Apr 5, 2026

Hey @hiepau1231, thanks for your PR!

From my understanding, this issue is mainly about organizing flags using tags. To move it forward, we’ll likely need to define a clear approach for categorizing and structuring flags based on those tags. Search/filtering feels like the final piece of the puzzle once that foundation is in place.

Curious to hear your thoughts on how you envision the tagging structure.

@hiepau1231
Copy link
Copy Markdown
Author

Hey @erka, thanks for the feedback!

You make a great point. I agree that a proper tagging/categorization system is the bigger goal here, and filtering is just one piece of it.

Here's how I was thinking about it:

Metadata as the foundation for tags

Flipt already has a metadata field on flags (free-form key/value). My thinking was that users could adopt a convention like metadata.tags: "release,experimental" or metadata.lifecycle: "qa" to categorize flags — and this filtering UI would make that convention immediately useful without any backend changes.

What a proper tagging structure could look like:

  1. A dedicated tags field on flags (string array) — stored in features.yaml alongside metadata
  2. Tag-based visual grouping on the Flags list page (collapsible sections or sidebar filters)
  3. Server-side filtering via ListFlagRequest (e.g. tags filter param)

Proposal: I can rescope this PR to just the client-side metadata filter as a quick win (it works with what exists today), and open a separate issue/discussion for the tagging data model design. Or, if you'd prefer to define the tagging structure first, I'm happy to contribute to that design and rebase this work on top of it.

What approach would you prefer?

@erka
Copy link
Copy Markdown
Contributor

erka commented Apr 6, 2026

Hey @hiepau1231

I’m open to any direction on this. Perhaps it would be good to document your proposal in the original issue so others can share their thoughts as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flag Grouping / Tagging inside Namespace

2 participants