-
Story
-
Resolution: Unresolved
-
Undefined
-
None
-
None
-
None
[UI] Add Architecture Filter to Mirroring Configuration
Summary
Add a multi-select component in the mirroring configuration UI to allow users to select which architectures to mirror from multi-architecture images. This provides a user-friendly way to configure architecture filtering without using the API directly.
Acceptance Criteria
- [ ] Multi-select component displays available architectures (amd64, arm64, ppc64le, s390x, 386, riscv64)
- [ ] Users can select one or more architectures to mirror
- [ ] Empty selection means "mirror all architectures" (with clear indication)
- [ ] Current architecture filter settings are displayed when editing existing mirror
- [ ] Architecture filter is saved when mirror configuration is saved
- [ ] Validation prevents saving invalid architecture configurations
- [ ] Help text explains the architecture filter feature
- [ ] UI follows PatternFly design patterns
Technical Requirements
Component Location
Directory: web/src/routes/RepositoryDetails/Mirroring/
Form Hook Updates
File: web/src/hooks/UseMirroringForm.ts
Add architecture filter to form state:
interface MirroringFormState { // ... existing fields ... architectureFilter: string[]; } const useMirroringForm = () => { const [formState, setFormState] = useState<MirroringFormState>({ // ... existing defaults ... architectureFilter: [], }); const setArchitectureFilter = (archs: string[]) => { setFormState(prev => ({ ...prev, architectureFilter: archs, })); }; return { // ... existing return values ... architectureFilter: formState.architectureFilter, setArchitectureFilter, }; };
API Resource Updates
File: web/src/resources/MirroringResource.ts
Update the resource to include architecture filter:
export interface MirrorConfig { // ... existing fields ... architecture_filter?: string[]; } export interface UpdateMirrorRequest { // ... existing fields ... architecture_filter?: string[]; }
Architecture Filter Component
File: web/src/routes/RepositoryDetails/Mirroring/ArchitectureFilter.tsx (new file)
import React from 'react'; import { FormGroup, Select, SelectOption, SelectVariant, Chip, ChipGroup, } from '@patternfly/react-core'; const AVAILABLE_ARCHITECTURES = [ { value: 'amd64', label: 'AMD64 (x86_64)' }, { value: 'arm64', label: 'ARM64 (aarch64)' }, { value: 'ppc64le', label: 'PowerPC 64 LE' }, { value: 's390x', label: 'IBM Z (s390x)' }, { value: '386', label: 'i386/i686' }, { value: 'riscv64', label: 'RISC-V 64' }, ]; interface ArchitectureFilterProps { selectedArchitectures: string[]; onChange: (archs: string[]) => void; isDisabled?: boolean; } export const ArchitectureFilter: React.FC<ArchitectureFilterProps> = ({ selectedArchitectures, onChange, isDisabled = false, }) => { const [isOpen, setIsOpen] = React.useState(false); const onSelect = (event: any, selection: string) => { if (selectedArchitectures.includes(selection)) { onChange(selectedArchitectures.filter(arch => arch !== selection)); } else { onChange([...selectedArchitectures, selection]); } }; const clearAll = () => { onChange([]); }; return ( <FormGroup label="Architecture Filter" fieldId="architecture-filter" helperText={ selectedArchitectures.length === 0 ? "All architectures will be mirrored. Select specific architectures to reduce storage usage." : `Only selected architectures will be mirrored. ${selectedArchitectures.length} selected.` } > <Select variant={SelectVariant.typeaheadMulti} typeAheadAriaLabel="Select architectures" onToggle={() => setIsOpen(!isOpen)} onSelect={onSelect} onClear={clearAll} selections={selectedArchitectures} isOpen={isOpen} aria-labelledby="architecture-filter" placeholderText="All architectures" isDisabled={isDisabled} > {AVAILABLE_ARCHITECTURES.map(arch => ( <SelectOption key={arch.value} value={arch.value}> {arch.label} </SelectOption> ))} </Select> </FormGroup> ); };
Integration in Mirroring Configuration
File: web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx
Add the architecture filter component:
import { ArchitectureFilter } from './ArchitectureFilter'; export const MirroringConfiguration: React.FC<Props> = ({ ... }) => { const { architectureFilter, setArchitectureFilter, // ... other form values } = useMirroringForm(); return ( <Form> {/* ... existing form fields ... */} <ArchitectureFilter selectedArchitectures={architectureFilter} onChange={setArchitectureFilter} isDisabled={!isEnabled} /> {/* ... rest of form ... */} </Form> ); };
Save Handler Updates
File: web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx
Update save handler to include architecture filter:
const handleSave = async () => { const mirrorConfig: UpdateMirrorRequest = { // ... existing fields ... architecture_filter: architectureFilter.length > 0 ? architectureFilter : null, }; await updateMirror(org, repo, mirrorConfig); };
Implementation Notes
Existing Patterns to Follow
- Form patterns: See existing form components in MirroringConfiguration.tsx
- PatternFly Select: Use @patternfly/react-core Select component
- Resource updates: See MirroringResource.ts for API integration patterns
- Frontend conventions: See web/AGENTS.md for React patterns
UI States
- Empty selection: Shows "All architectures" placeholder, helper text explains all will be mirrored
- One or more selected: Shows chips for selected architectures, helper text shows count
- Loading: Select is disabled while loading mirror config
- Disabled: Select is disabled when mirroring is disabled
Accessibility
- Use proper ARIA labels
- Ensure keyboard navigation works
- Provide clear helper text
Validation
- No validation needed for empty selection (valid = all architectures)
- Architecture values are constrained by SelectOption values
- Server-side validation handles any edge cases
Dependencies
- Story 05: Mirror API must support architecture_filter field
Testing Requirements
Component Tests
File: web/src/routes/RepositoryDetails/Mirroring/__tests__/ArchitectureFilter.test.tsx (new file)
describe('ArchitectureFilter', () => { it('renders with no selection', () => { // Verify placeholder text "All architectures" }); it('allows selecting multiple architectures', () => { // Select amd64, then arm64 // Verify both are in selectedArchitectures }); it('allows deselecting architectures', () => { // Start with amd64 selected // Click amd64 again to deselect // Verify empty selection }); it('clears all selections', () => { // Start with multiple selections // Click clear button // Verify empty selection }); it('is disabled when isDisabled is true', () => { // Verify select is not interactive }); it('displays helper text for empty selection', () => { // Verify "All architectures will be mirrored" text }); it('displays helper text with count for selection', () => { // Select 2 architectures // Verify "2 selected" in helper text }); });
Integration Tests
File: web/cypress/e2e/mirroring.cy.ts (if Cypress tests exist)
describe('Mirroring Architecture Filter', () => { it('saves architecture filter with mirror config', () => { // Navigate to mirror settings // Select architectures // Save // Verify saved via API }); it('loads existing architecture filter on edit', () => { // Create mirror with filter via API // Navigate to mirror settings // Verify correct architectures are selected }); });
Definition of Done
- [ ] Code implemented and follows project conventions
- [ ] All acceptance criteria met
- [ ] Component tests written and passing
- [ ] UI is accessible (keyboard navigation, ARIA labels)
- [ ] UI follows PatternFly design patterns
- [ ] Integration with API verified
- [ ] Code reviewed and approved