Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions console/integration-tests/tests/pattern-catalog-page.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down
50 changes: 42 additions & 8 deletions console/src/components/PatternCatalogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,18 @@ const TIER_SVG_COLORS: Record<string, { filled: string; outline: string }> = {
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<string, number> = {
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 (
<svg
width="16"
Expand Down Expand Up @@ -167,7 +174,7 @@ export default function PatternCatalogPage() {
const [catalogImage, setCatalogImage] = React.useState<string | null>(null);
const [catalogDescription, setCatalogDescription] = React.useState<string | undefined>();
const [catalogLogo, setCatalogLogo] = React.useState<string | undefined>();
const [selectedTiers, setSelectedTiers] = React.useState<Set<string>>(new Set(['maintained']));
const [selectedTiers, setSelectedTiers] = React.useState<Set<string>>(new Set());
const [tierSelectOpen, setTierSelectOpen] = React.useState(false);

const loadData = React.useCallback(() => {
Expand All @@ -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],
Expand Down Expand Up @@ -284,7 +314,7 @@ export default function PatternCatalogPage() {
)}
>
<SelectList>
{['maintained', 'tested', 'sandbox'].map((tier) => (
{availableTiers.map((tier) => (
<SelectOption
key={tier}
value={tier}
Expand Down Expand Up @@ -315,7 +345,11 @@ export default function PatternCatalogPage() {
<Tooltip content={TIER_DESCRIPTIONS[pattern.tier] || pattern.tier}>
<Label
color={TIER_COLORS[pattern.tier] || 'grey'}
icon={<TierIcon tier={pattern.tier} />}
icon={
TIER_SVG_COLORS[pattern.tier] ? (
<TierIcon tier={pattern.tier} />
) : undefined
}
>
{pattern.tier}
</Label>
Expand Down
4 changes: 3 additions & 1 deletion console/src/components/SecretForm/FileField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ export const FileField: React.FC<FileFieldProps> = ({ field, value, onChange, fi
</HelperTextItem>
)}
{field.description && (
<HelperTextItem id={`file-${field.name}-helpertext`}>{field.description}</HelperTextItem>
<HelperTextItem id={`file-${field.name}-helpertext`}>
{field.description}
</HelperTextItem>
)}
</HelperText>
</FileUploadHelperText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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] || '';

Expand Down
2 changes: 1 addition & 1 deletion console/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down