Skip to content

Commit

Permalink
fix: Display selected and prioritized category badges for easy select…
Browse files Browse the repository at this point in the history
…ion in the catalog

Fixes: #1041
  • Loading branch information
igarashitm authored and lordrip committed May 2, 2024
1 parent c235475 commit 062330e
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 9 deletions.
6 changes: 3 additions & 3 deletions packages/ui-tests/cypress/e2e/componentCatalog.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ describe('Catalog related tests', () => {

cy.get('[data-testid="tile-aws2-redshift-data"]').should('be.visible');

cy.get('button[aria-label="Close cloud"]').click();
cy.get('button[aria-label="Close database"]').click();
cy.get('button[aria-label="Close serverless"]').click();
cy.get('[data-testid="button-catalog-tag-cloud"]').click();
cy.get('[data-testid="button-catalog-tag-database"]').click();
cy.get('[data-testid="button-catalog-tag-serverless"]').click();
cy.contains('h2', 'Showing 1 elements').should('not.exist');
});

Expand Down
8 changes: 8 additions & 0 deletions packages/ui/src/components/Catalog/Catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CatalogLayout, ITile } from './Catalog.models';
import './Catalog.scss';
import { CatalogFilter } from './CatalogFilter';
import { filterTiles } from './filter-tiles';
import { sortTags } from './sort-tags';

interface CatalogProps {
/** Tiles list */
Expand All @@ -21,6 +22,11 @@ export const Catalog: FunctionComponent<PropsWithChildren<CatalogProps>> = (prop
const [searchTerm, setSearchTerm] = useDebounceValue('', 500, { trailing: true });
const [filterTags, setFilterTags] = useState<string[]>([]);

/** All tags, sorted to have selected and prioritized tags first */
const { sortedTags, overflowIndex } = useMemo(() => {
return sortTags(props.tiles, filterTags);
}, [filterTags, props.tiles]);

/** Filter by selected group */
const filteredTilesByGroup = useMemo(() => {
return filterTiles(props.tiles, { searchTerm, searchTags: filterTags });
Expand Down Expand Up @@ -68,6 +74,8 @@ export const Catalog: FunctionComponent<PropsWithChildren<CatalogProps>> = (prop
className="catalog__filter"
searchTerm={searchTerm}
groups={tilesGroups}
tags={sortedTags}
tagsOverflowIndex={overflowIndex}
layouts={[CatalogLayout.Gallery, CatalogLayout.List]}
activeGroups={activeGroups}
activeLayout={activeLayout}
Expand Down
45 changes: 39 additions & 6 deletions packages/ui/src/components/Catalog/CatalogFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import {
import { FunctionComponent, useEffect, useRef } from 'react';
import { CatalogLayout } from './Catalog.models';
import { CatalogLayoutIcon } from './CatalogLayoutIcon';
import { TimesCircleIcon } from '@patternfly/react-icons';

interface CatalogFilterProps {
className?: string;
searchTerm: string;
groups: { name: string; count: number }[];
tags: string[];
tagsOverflowIndex: number;
layouts: CatalogLayout[];
activeGroups: string[];
activeLayout: CatalogLayout;
Expand All @@ -36,8 +39,16 @@ export const CatalogFilter: FunctionComponent<CatalogFilterProps> = (props) => {
inputRef.current?.focus();
}, []);

const onClose = (tag: string) => {
props.setFilterTags(props.filterTags.filter((savedTag) => savedTag !== tag));
const onToggleTag = (tag: string) => {
const isToggled = props.filterTags.includes(tag);

props.setFilterTags(
isToggled ? props.filterTags.filter((savedTag) => savedTag !== tag) : props.filterTags.concat(tag),
);
};

const handleClearFilterTags = () => {
props.setFilterTags([]);
};

const toggleActiveGroup = (selected: boolean, group: string) => {
Expand Down Expand Up @@ -107,10 +118,32 @@ export const CatalogFilter: FunctionComponent<CatalogFilterProps> = (props) => {
</FormGroup>
</GridItem>
</Grid>
<LabelGroup categoryName="Filtered tags" numLabels={10}>
{props.filterTags.map((tag, index) => (
<Label key={tag + index} id={tag + index} color="blue" onClose={() => onClose(tag)} isCompact>
{tag}
<LabelGroup
categoryName={'Filter Categories'}
numLabels={props.filterTags.length > 0 ? props.tagsOverflowIndex + 1 : props.tagsOverflowIndex}
>
{props.filterTags.length > 0 && (
<Label
key="clear"
id="clear"
color="grey"
variant="filled"
onClick={handleClearFilterTags}
icon={<TimesCircleIcon />}
>
<b>Clear</b>
</Label>
)}
{props.tags.map((tag, index) => (
<Label
key={tag + index}
id={tag + index}
data-testid={`button-catalog-tag-${tag}`}
color={props.filterTags.includes(tag) ? 'blue' : 'grey'}
onClick={() => onToggleTag(tag)}
variant={props.filterTags.includes(tag) ? 'filled' : 'outline'}
>
{props.filterTags.includes(tag) ? <b>{tag}</b> : <>{tag}</>}
</Label>
))}
</LabelGroup>
Expand Down
46 changes: 46 additions & 0 deletions packages/ui/src/components/Catalog/sort-tags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { sortTags } from './sort-tags';
import { ITile } from './Catalog.models';

describe('sortTags()', () => {
it('should return empty', () => {
const tiles: ITile[] = [];
const filterTags: string[] = [];
const { sortedTags, overflowIndex } = sortTags(tiles, filterTags);
expect(sortedTags.length).toBe(0);
expect(overflowIndex).toBe(0);
});

it('should return empty if filter tag is not really used', () => {
const tiles: ITile[] = [];
const filterTags = ['eip'];
const { sortedTags, overflowIndex } = sortTags(tiles, filterTags);
expect(sortedTags.length).toBe(0);
expect(overflowIndex).toBe(0);
});

it('should sort tags', () => {
const tiles: ITile[] = [
{
type: 'component',
name: '1',
title: '1',
tags: ['transformation', 'aaa-comp'],
},
{
type: 'processor',
name: '2',
title: '2',
tags: ['transformation', 'eip', 'aaa-proc'],
},
];
let filterTags: string[] = [];
const { sortedTags, overflowIndex } = sortTags(tiles, filterTags);
expect(sortedTags.length).toBe(4);
expect(sortedTags[0]).toEqual('aaa-proc');
expect(overflowIndex).toBe(2);
filterTags = ['eip'];
const { sortedTags: t1, overflowIndex: i1 } = sortTags(tiles, filterTags);
expect(t1[0]).toEqual('eip');
expect(i1).toBe(3);
});
});
44 changes: 44 additions & 0 deletions packages/ui/src/components/Catalog/sort-tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ITile } from './Catalog.models';

/** Tags to keep from overflowing. */
const additionalPriorityTags = ['database', 'cloud'];

/** The number of tags to start using priority and filter tags to calculate overflowing index. */
const overflowThreshold = 0;

/**
* Extract all tags from tiles and sort it alphabetically as well as to pull prioritized tags and
* {@link filterTags} up.
* As a result, the prioritized tags and the ones in the filter are always shown. If the added number of
* prioritized tags and {@link filterTags} exceeds {@link overflowThreshold}, all other tags overflow
* behind the "N more" label, Otherwise the {@link overflowThreshold} number of tags are shown and kept
* from overflowing.
* The prioritized tags are either (1) the tags assigned to processors other than "eip", or
* (2) {@link additionalPriorityTags}.
*/
export const sortTags = (allTiles: ITile[], filterTags: string[]): { sortedTags: string[]; overflowIndex: number } => {
const priorityTags = [...additionalPriorityTags];
const sortedTags = allTiles
.reduce((acc, tile) => {
tile.tags.forEach((tag) => {
!acc.includes(tag) && acc.push(tag);
tile.type === 'processor' && tag !== 'eip' && !priorityTags.includes(tag) && priorityTags.push(tag);
});
return acc;
}, [] as string[])
.sort((a, b) => {
if (filterTags.includes(a) && !filterTags.includes(b)) return -1;
if (filterTags.includes(b) && !filterTags.includes(a)) return 1;
if (priorityTags.includes(a) && !priorityTags.includes(b)) return -1;
if (priorityTags.includes(b) && !priorityTags.includes(a)) return 1;

const lowerA = a.toLowerCase();
const lowerB = b.toLowerCase();
if (lowerA < lowerB) return -1;
if (lowerA > lowerB) return 1;
return 0;
});
const num = sortedTags.filter((tag) => filterTags.includes(tag) || priorityTags.includes(tag)).length;
const overflowIndex = num > overflowThreshold ? num : overflowThreshold;
return { sortedTags, overflowIndex };
};

0 comments on commit 062330e

Please sign in to comment.