From 0c15eebbb488a00988cdfd02b22aceec2b5f0e45 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 27 May 2026 09:31:39 +0200 Subject: [PATCH] Try and add support for custom tiers --- .../tests/pattern-catalog-page.cy.ts | 40 ++++++++------- console/src/components/PatternCatalogPage.tsx | 50 ++++++++++++++++--- .../src/components/SecretForm/FileField.tsx | 4 +- .../SecretFormExpandableSections.tsx | 6 +-- console/src/types.ts | 2 +- 5 files changed, 69 insertions(+), 33 deletions(-) diff --git a/console/integration-tests/tests/pattern-catalog-page.cy.ts b/console/integration-tests/tests/pattern-catalog-page.cy.ts index 8625c092c..854eeff58 100644 --- a/console/integration-tests/tests/pattern-catalog-page.cy.ts +++ b/console/integration-tests/tests/pattern-catalog-page.cy.ts @@ -60,32 +60,36 @@ describe('Pattern Catalog Page', () => { }); }); - it('tier filter dropdown shows all tier options', () => { + it('tier filter dropdown shows tier options', () => { visitCatalog(); - // Default filter shows "Maintained"; click the toggle button - cy.contains('button', 'Maintained').click(); - // Options are capitalized ("Tested", "Sandbox") and unique to the dropdown - cy.contains('Tested').should('be.visible'); - cy.contains('Sandbox').should('be.visible'); - // Close dropdown by clicking the toggle again - cy.contains('button', 'Maintained').click(); + // Open the tier filter dropdown + cy.get('#tier-filter').closest('.pf-v6-c-select').find('button').first().click(); + // At least one selectable option should be visible + cy.get('[role="option"]').should('have.length.greaterThan', 0); + // Close dropdown + cy.get('#tier-filter').closest('.pf-v6-c-select').find('button').first().click(); }); - it('selecting all tiers shows at least as many cards as maintained only', () => { + it('selecting all tiers shows at least as many cards as the default selection', () => { visitCatalog(); cy.get('.patterns-operator__card') .its('length') - .then((maintainedCount) => { - // Open filter and add Tested - cy.contains('button', 'Maintained').click(); - cy.contains('Tested').click(); - // Dropdown may close after selection; re-open to add Sandbox - cy.contains('button', /Maintained/).click(); - cy.contains('Sandbox').click(); + .then((defaultCount) => { + // Open filter dropdown + cy.get('#tier-filter').closest('.pf-v6-c-select').find('button').first().click(); + // Select every unchecked tier option + cy.get('[role="option"]').each(($option) => { + const checkbox = $option.find('input[type="checkbox"]'); + if (checkbox.length && !checkbox.is(':checked')) { + cy.wrap($option).click(); + // Re-open dropdown if it closed + cy.get('#tier-filter').closest('.pf-v6-c-select').find('button').first().click(); + } + }); // Close dropdown cy.get('body').click(0, 0); - // With more tiers selected, card count should be >= maintained only - cy.get('.patterns-operator__card').should('have.length.gte', maintainedCount); + // With all tiers selected, card count should be >= default selection + cy.get('.patterns-operator__card').should('have.length.gte', defaultCount); }); }); diff --git a/console/src/components/PatternCatalogPage.tsx b/console/src/components/PatternCatalogPage.tsx index 962d2a7f8..922f03bdf 100644 --- a/console/src/components/PatternCatalogPage.tsx +++ b/console/src/components/PatternCatalogPage.tsx @@ -115,11 +115,18 @@ const TIER_SVG_COLORS: Record = { sandbox: { filled: '#f0ab00', outline: '#f0ab00' }, }; -function TierIcon({ tier }: { tier: string }) { - const colors = TIER_SVG_COLORS[tier] || TIER_SVG_COLORS.sandbox; - // 3 horizontal bars: bottom=bar1, middle=bar2, top=bar3 - // maintained: all 3 filled; tested: 2 filled + 1 outline; sandbox: 1 filled + 2 outline - const filledCount = tier === 'maintained' ? 3 : tier === 'tested' ? 2 : 1; +const KNOWN_TIER_ORDER = ['maintained', 'tested', 'sandbox']; + +const TIER_FILLED_BARS: Record = { + maintained: 3, + tested: 2, + sandbox: 1, +}; + +function TierIcon({ tier }: { tier: string }): React.ReactElement | null { + const colors = TIER_SVG_COLORS[tier]; + if (!colors) return null; + const filledCount = TIER_FILLED_BARS[tier] ?? 1; return ( (null); const [catalogDescription, setCatalogDescription] = React.useState(); const [catalogLogo, setCatalogLogo] = React.useState(); - const [selectedTiers, setSelectedTiers] = React.useState>(new Set(['maintained'])); + const [selectedTiers, setSelectedTiers] = React.useState>(new Set()); const [tierSelectOpen, setTierSelectOpen] = React.useState(false); const loadData = React.useCallback(() => { @@ -191,6 +198,29 @@ export default function PatternCatalogPage() { loadData(); }, [loadData]); + const availableTiers = React.useMemo(() => { + const tierSet = new Set(patterns.map((p) => p.tier)); + return Array.from(tierSet).sort((a, b) => { + const ai = KNOWN_TIER_ORDER.indexOf(a); + const bi = KNOWN_TIER_ORDER.indexOf(b); + if (ai !== -1 && bi !== -1) return ai - bi; + if (ai !== -1) return -1; + if (bi !== -1) return 1; + return a.localeCompare(b, undefined, { sensitivity: 'base' }); + }); + }, [patterns]); + + const defaultsApplied = React.useRef(false); + React.useEffect(() => { + if (defaultsApplied.current || availableTiers.length === 0) return; + defaultsApplied.current = true; + if (availableTiers.includes('maintained')) { + setSelectedTiers(new Set(['maintained'])); + } else { + setSelectedTiers(new Set(availableTiers)); + } + }, [availableTiers]); + const filteredPatterns = React.useMemo( () => (selectedTiers.size === 0 ? patterns : patterns.filter((p) => selectedTiers.has(p.tier))), [patterns, selectedTiers], @@ -284,7 +314,7 @@ export default function PatternCatalogPage() { )} > - {['maintained', 'tested', 'sandbox'].map((tier) => ( + {availableTiers.map((tier) => ( } + icon={ + TIER_SVG_COLORS[pattern.tier] ? ( + + ) : undefined + } > {pattern.tier} diff --git a/console/src/components/SecretForm/FileField.tsx b/console/src/components/SecretForm/FileField.tsx index 76155594f..4adb6e762 100644 --- a/console/src/components/SecretForm/FileField.tsx +++ b/console/src/components/SecretForm/FileField.tsx @@ -68,7 +68,9 @@ export const FileField: React.FC = ({ field, value, onChange, fi )} {field.description && ( - {field.description} + + {field.description} + )} diff --git a/console/src/components/SecretForm/SecretFormExpandableSections.tsx b/console/src/components/SecretForm/SecretFormExpandableSections.tsx index deec422c9..4563c6d4e 100644 --- a/console/src/components/SecretForm/SecretFormExpandableSections.tsx +++ b/console/src/components/SecretForm/SecretFormExpandableSections.tsx @@ -32,11 +32,7 @@ export function SecretFormExpandableSections({ }: SecretFormExpandableSectionsProps) { const { t } = useTranslation('plugin__patterns-operator-console-plugin'); - const renderField = ( - secret: SecretDefinition, - field: SecretField, - uploadFieldError?: string, - ) => { + const renderField = (secret: SecretDefinition, field: SecretField, uploadFieldError?: string) => { const fieldType = getSecretFieldKind(field); const value = secretFormData[secret.name]?.[field.name] || ''; diff --git a/console/src/types.ts b/console/src/types.ts index 3796a335d..1d8eb3e8e 100644 --- a/console/src/types.ts +++ b/console/src/types.ts @@ -38,7 +38,7 @@ export interface Pattern { issues_url: string; docs_url: string; ci_url: string; - tier: 'maintained' | 'tested' | 'sandbox'; + tier: string; owners: string[]; org: string; clustergroupname: string;