Skip to content

[FEATURE] Support per-column value filtering in the Table component - #425

Open
adrianSepiol wants to merge 5 commits into
perses:mainfrom
adrianSepiol:feature/support-column-filtering
Open

adrianSepiol wants to merge 5 commits into
perses:mainfrom
adrianSepiol:feature/support-column-filtering

Conversation

@adrianSepiol

Copy link
Copy Markdown
Contributor

Description

Needed for: perses/perses#4607

Adds an opt-in column filter to table headers. Columns with enableColumnFilter: true render a filter icon that opens a searchable, checkbox-list popover of the column's unique values; selecting values filters the rows.

This brings column filtering into the shared Table and enables plugins/table to drop its custom filtering implementation. Today TablePanel hand-rolls its own ColumnFilterDropdown, columnFilters state, filteredData memo, and a filter row kept in horizontal sync via refs.

To test it with tags column update TAGS_COLUMN in DashboardTreeList.tsx with this properties:

{
  accessorKey: 'tags', // changed from 'tagsSearchValue'
  enableColumnFilter: true,
  getUniqueValues: (rows): Set<string> => {
    return rows.tags ?? [];
  },
  filterFn: 'arrIncludesSome',
  enableGlobalFilter: true,
}

Screenshots

Screenshot 2026-11-02 at 15 08 54

Checklist

  • Pull request has a descriptive title and context useful to a reviewer.
  • Pull request title follows the [<catalog_entry>] <commit message> naming convention using one of the
    following catalog_entry values: FEATURE, ENHANCEMENT, BUGFIX, BREAKINGCHANGE, DOC,IGNORE.
  • All commits have DCO signoffs.

UI Changes

  • Changes that impact the UI include screenshots and/or screencasts of the relevant changes.
  • Code follows the UI guidelines.
  • E2E tests are stable and unlikely to be flaky.
    See e2e docs for more details. Common issues include:
    • Is the data inconsistent? You need to mock API requests.
    • Does the time change? You need to use consistent time values or mock time utilities.
    • Does it have loading states? You need to wait for loading to complete.

…able headers

Signed-off-by: Adrian Sepiół <a.sepiol@sap.com>
@adrianSepiol
adrianSepiol force-pushed the feature/support-column-filtering branch from 47e52e6 to c839b22 Compare November 9, 2026 12:37
display: 'flex',
flexWrap: 'nowrap',
flexDirection: 'row',
justifyContent: 'space-between',

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.

nit: This changes all previous headers that were center aligned to left aligned, for example actions. Should we apply this kind of alignment only when the filter is enabled?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

addressed in 570f688 should work as before

sx={styles.search}
/>
<Divider />
<List dense disablePadding aria-labelledby={labelId} sx={styles.list}>

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.

Can this list get big enough that would require virtualization?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There might be a lot of entries, I've added virtualization in efe5c3f

const [searchKey, setSearchKey] = useState(0);

const sortedEntries = useMemo(
() => [...uniqueValues.entries()].toSorted(([a], [b]) => String(a).localeCompare(String(b))),

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.

if we just use local compare numbers will be sorted as text

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in b774292

const handlePopoverClick = useCallback((e: React.MouseEvent) => e.stopPropagation(), []);
const handlePopoverKeyUp = useCallback((e: React.KeyboardEvent) => e.stopPropagation(), []);

const handleToggleAll = useCallback((): void => {

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.

When a filter is selected and then the table data changes to values that do not contain the value in the filter, it seems the table still filters with the non existent value, leading to an empty table and the filter no longer on the list to uncheck it. Clearing all does not reset the filter correctly so the table is filtered with probably 0 results without a way to clearing them.

@adrianSepiol adrianSepiol Nov 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed in e85b6bd. It is now possible to filter out old values:

Screen.Recording.2026-11-10.at.13.39.11.mov

Signed-off-by: Adrian Sepiół <a.sepiol@sap.com>
…mixed value types

Signed-off-by: Adrian Sepiół <a.sepiol@sap.com>
…r values and update selection behavior

Signed-off-by: Adrian Sepiół <a.sepiol@sap.com>
@adrianSepiol
adrianSepiol force-pushed the feature/support-column-filtering branch from 53c1f39 to e85b6bd Compare November 10, 2026 12:41
Signed-off-by: Adrian Sepiół <a.sepiol@sap.com>
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