From 96fb624789788864afe63374b148fcba9d1d768d Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 20 May 2026 10:33:17 +0100 Subject: [PATCH] Promote workload runtime filter to inline chip toggle The Runtime axis was hidden inside the "+ Filter" combobox under a "Properties" optgroup with at most two real choices (docker, podman), sitting next to Status / Group by / Metric display which are already chip toggles. Render it inline alongside Status so the binary axis is one click instead of two, and hide the filter entirely when fewer than two distinct runtimes are present so single-runtime deployments are not shown a degenerate All/ control. The leftover "+ Filter" select now carries only the Node scope. --- .../components/Workloads/WorkloadsFilter.tsx | 1 + .../__tests__/WorkloadsFilter.test.tsx | 19 +++++++++++++------ .../workloadFilterConfigModel.test.ts | 12 +++++++++++- .../Workloads/workloadFilterConfigModel.ts | 2 +- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx b/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx index 8c4089993a..b911c28369 100644 --- a/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx +++ b/frontend-modern/src/components/Workloads/WorkloadsFilter.tsx @@ -179,6 +179,7 @@ export const WorkloadsFilter: Component = (props) => { id: runtimeFilter.id ?? 'workloads-runtime', label: runtimeFilter.label ?? 'Runtime', group: 'properties', + inline: true, value: () => runtimeFilter.value, setValue: runtimeFilter.onChange, defaultValue: '', diff --git a/frontend-modern/src/components/Workloads/__tests__/WorkloadsFilter.test.tsx b/frontend-modern/src/components/Workloads/__tests__/WorkloadsFilter.test.tsx index eba765945f..fea97d56fe 100644 --- a/frontend-modern/src/components/Workloads/__tests__/WorkloadsFilter.test.tsx +++ b/frontend-modern/src/components/Workloads/__tests__/WorkloadsFilter.test.tsx @@ -269,6 +269,7 @@ describe('WorkloadsFilter', () => { options: [ { value: '', label: 'All runtimes' }, { value: 'docker', label: 'docker' }, + { value: 'podman', label: 'podman' }, ], onChange: vi.fn(), }, @@ -329,6 +330,7 @@ describe('WorkloadsFilter', () => { options: [ { value: '', label: 'All runtimes' }, { value: 'docker', label: 'docker' }, + { value: 'podman', label: 'podman' }, ], onChange: runtimeOnChange, }, @@ -522,7 +524,7 @@ describe('WorkloadsFilter', () => { }); describe('container runtime filter', () => { - it('appears in the direct selector only when viewMode is a container variant and containerRuntimeFilter is provided', () => { + it('renders inline runtime chips when viewMode is a container variant and containerRuntimeFilter is provided', () => { const props = makeProps({ viewMode: vi.fn(() => 'container' as const), containerRuntimeFilter: { @@ -530,15 +532,18 @@ describe('WorkloadsFilter', () => { options: [ { value: '', label: 'All runtimes' }, { value: 'docker', label: 'docker' }, + { value: 'podman', label: 'podman' }, ], onChange: vi.fn(), }, }); render(() => ); - expect(addFilterOption('Runtime', 'docker')).toBeInTheDocument(); + const runtimeGroup = inlineFilterGroup('Runtime'); + expect(within(runtimeGroup).getByRole('button', { name: 'docker' })).toBeInTheDocument(); + expect(within(runtimeGroup).getByRole('button', { name: 'podman' })).toBeInTheDocument(); }); - it('does not appear when viewMode is not a container variant', () => { + it('does not render when viewMode is not a container variant', () => { const props = makeProps({ viewMode: vi.fn(() => 'vm' as const), containerRuntimeFilter: { @@ -546,15 +551,16 @@ describe('WorkloadsFilter', () => { options: [ { value: '', label: 'All runtimes' }, { value: 'docker', label: 'docker' }, + { value: 'podman', label: 'podman' }, ], onChange: vi.fn(), }, }); render(() => ); - expect(screen.queryByRole('combobox', { name: 'Filter' })).not.toBeInTheDocument(); + expect(screen.queryByRole('group', { name: 'Runtime' })).not.toBeInTheDocument(); }); - it('calls onChange when runtime selection changes through the direct selector', () => { + it('calls onChange when a runtime chip is clicked', () => { const onChange = vi.fn(); render(() => ( { options: [ { value: '', label: 'All runtimes' }, { value: 'docker', label: 'docker' }, + { value: 'podman', label: 'podman' }, ], onChange, }, })} /> )); - pickFromMenu('Runtime', 'docker'); + pickInlineFilter('Runtime', 'docker'); expect(onChange).toHaveBeenCalledWith('docker'); }); }); diff --git a/frontend-modern/src/components/Workloads/__tests__/workloadFilterConfigModel.test.ts b/frontend-modern/src/components/Workloads/__tests__/workloadFilterConfigModel.test.ts index 75eeb0b6cc..3dfc8106b2 100644 --- a/frontend-modern/src/components/Workloads/__tests__/workloadFilterConfigModel.test.ts +++ b/frontend-modern/src/components/Workloads/__tests__/workloadFilterConfigModel.test.ts @@ -46,6 +46,16 @@ describe('workloadFilterConfigModel', () => { runtimeOptions: ['docker'], onChange, }), + ).toBeUndefined(); + + expect( + buildWorkloadsContainerRuntimeFilterConfig({ + isWorkloadsRoute: true, + viewMode: 'app-container', + containerRuntime: 'docker', + runtimeOptions: ['docker', 'podman'], + onChange, + }), ).toBeDefined(); expect( @@ -53,7 +63,7 @@ describe('workloadFilterConfigModel', () => { isWorkloadsRoute: true, viewMode: 'vm', containerRuntime: 'docker', - runtimeOptions: ['docker'], + runtimeOptions: ['docker', 'podman'], onChange, }), ).toBeUndefined(); diff --git a/frontend-modern/src/components/Workloads/workloadFilterConfigModel.ts b/frontend-modern/src/components/Workloads/workloadFilterConfigModel.ts index 25c63aec1a..809641f8fd 100644 --- a/frontend-modern/src/components/Workloads/workloadFilterConfigModel.ts +++ b/frontend-modern/src/components/Workloads/workloadFilterConfigModel.ts @@ -45,7 +45,7 @@ export const buildWorkloadsContainerRuntimeFilterConfig = ({ }: WorkloadsContainerRuntimeFilterConfigOptions): WorkloadsToolbarFilterConfig | undefined => { if (!isWorkloadsRoute && !allowEmbeddedScopeFilters) return undefined; if (!isContainerWorkloadViewMode(viewMode)) return undefined; - if (runtimeOptions.length === 0) return undefined; + if (runtimeOptions.length < 2) return undefined; return { id: 'workloads-container-runtime-filter',