Uploaded image for project: 'Project Quay'
  1. Project Quay
  2. PROJQUAY-10260

[UI] Add Architecture Filter to Mirroring Configuration

XMLWordPrintable

    • Icon: Story Story
    • Resolution: Unresolved
    • Icon: Undefined Undefined
    • None
    • None
    • None
    • Security & Compliance
    • False
    • Hide

      None

      Show
      None
    • False
    • Not Selected

      [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

      1. Empty selection: Shows "All architectures" placeholder, helper text explains all will be mirrored
      2. One or more selected: Shows chips for selected architectures, helper text shows count
      3. Loading: Select is disabled while loading mirror config
      4. 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

              marckok Marcus Kok
              marckok Marcus Kok
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: