Skip to content

Commit

Permalink
RHOAIENG-16233: Filter workspaces
Browse files Browse the repository at this point in the history
Signed-off-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com>
  • Loading branch information
Elay Aharoni (EXT-Nokia) committed Dec 11, 2024
1 parent 9cf6a25 commit 0ad2045
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 74 deletions.
2 changes: 1 addition & 1 deletion workspaces/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.11.0",
"css-minimizer-webpack-plugin": "^5.0.1",
"cypress": "^13.15.0",
"cypress": "^13.16.1",
"cypress-axe": "^1.5.0",
"cypress-high-resolution": "^1.0.0",
"cypress-mochawesome-reporter": "^3.8.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default defineConfig({
},
defaultCommandTimeout: 10000,
e2e: {
baseUrl: BASE_URL,
baseUrl: env.CY_MOCK ? BASE_URL : 'http://localhost:9000',
specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`,
experimentalInteractiveRunEvents: true,
setupNodeEvents(on, config) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { home } from '~/__tests__/cypress/cypress/pages/home';

const useFilter = (filterName: string, searchValue: string) => {
cy.get("[id$='filter-workspaces-dropdown']").click();
cy.get(`[id$='filter-workspaces-dropdown-${filterName}']`).click();
cy.get("[id$='filter-workspaces-search-input']").type(searchValue);
cy.get("[class$='pf-v6-c-toolbar__group']").contains(filterName);
cy.get("[class$='pf-v6-c-toolbar__group']").contains(searchValue);
};

describe('Application', () => {
it('filter rows with single filter', () => {
home.visit();
useFilter('Name', 'My');
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2);
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
cy.get("[id$='workspaces-table-row-2']").contains('My Other Jupyter Notebook');
});

it('filter rows with multiple filters', () => {
home.visit();
useFilter('Name', 'My');
useFilter('Pod Config', 'Small');
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1);
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
});

it('filter rows with multiple filters and remove one', () => {
home.visit();
useFilter('Name', 'My');
useFilter('Pod Config', 'Small');
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1);
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
cy.get("[class$='pf-v6-c-label-group__close']").eq(1).click();
cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Pod Config');
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2);
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
cy.get("[id$='workspaces-table-row-2']").contains('My Other Jupyter Notebook');
});

it('filter rows with multiple filters and remove all', () => {
home.visit();
useFilter('Name', 'My');
useFilter('Pod Config', 'Small');
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1);
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
cy.get('*').contains('Clear all filters').click();
cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Pod Config');
cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Name');
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 4);
});
});
111 changes: 77 additions & 34 deletions workspaces/frontend/src/app/Generic components/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,24 @@ import {
import { FilterIcon } from '@patternfly/react-icons';

interface FilterProps {
onFilter: (activeAttributeMenu: string, value: string) => void;
id: string;
onFilter: (filters: { filterName: string; value: string }[]) => void;
columnNames: { [key: string]: string };
}

const Filter: React.FC<FilterProps> = ({ onFilter, columnNames }) => {
const [activeFilter, setActiveFilter] = React.useState<string>(Object.values(columnNames)[0]);
const [searchValue, setSearchValue] = React.useState('');
const [isFilterMenuOpen, setIsFilterMenuOpen] = React.useState(false);
interface FilterObj {
filterName: string;
value: string;
}

const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
const [activeFilter, setActiveFilter] = React.useState<FilterObj>({
filterName: Object.values(columnNames)[0],
value: '',
});
const [searchValue, setSearchValue] = React.useState<string>('');
const [isFilterMenuOpen, setIsFilterMenuOpen] = React.useState<boolean>(false);
const [filters, setFilters] = React.useState<FilterObj[]>([]);

const filterToggleRef = React.useRef<MenuToggleElement | null>(null);
const filterMenuRef = React.useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -81,29 +91,60 @@ const Filter: React.FC<FilterProps> = ({ onFilter, columnNames }) => {
setIsFilterMenuOpen(!isFilterMenuOpen);
};

const addFilter = (filterObj: FilterObj) => {
const index = filters.findIndex((filter) => filter.filterName === filterObj.filterName);
const newFilters = filters;
if (index !== -1) {
newFilters[index] = filterObj;
} else {
newFilters.push(filterObj);
}
setFilters(newFilters);
};

const onSearchChange = (value: string) => {
const newFilter = { filterName: activeFilter.filterName, value };
setSearchValue(value);
setActiveFilter(newFilter);
addFilter(newFilter);
onFilter(filters);
};

const onDeleteLabelGroup = (filter: FilterObj) => {
const newFilters = filters.filter((filter1) => filter1.filterName !== filter.filterName);
setFilters(newFilters);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
filter.filterName === activeFilter.filterName && setSearchValue('');
onFilter(newFilters);
};

const onFilterSelect = (itemId: string | number | undefined) => {
setIsFilterMenuOpen(!isFilterMenuOpen);
const index = filters.findIndex((filter) => filter.filterName === itemId);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
index === -1 ? setSearchValue('') : setSearchValue(filters[index].value);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setActiveFilter({ filterName: itemId, value: searchValue });
};

const FilterMenuToggle = (
<MenuToggle
ref={filterToggleRef}
onClick={onFilterToggleClick}
isExpanded={isFilterMenuOpen}
icon={<FilterIcon />}
>
{activeFilter}
{activeFilter.filterName}
</MenuToggle>
);

const filterMenu = (
<Menu
ref={filterMenuRef}
onSelect={(_ev, itemId) => {
setActiveFilter(itemId);
setIsFilterMenuOpen(!isFilterMenuOpen);
}}
>
<Menu ref={filterMenuRef} onSelect={(_ev, itemId) => onFilterSelect(itemId)}>
<MenuContent>
<MenuList>
{Object.values(columnNames).map((name: string) => (
<MenuItem key={name} itemId={name}>
<MenuItem id={`${id}-dropdown-${name}`} key={name} itemId={name}>
{name}
</MenuItem>
))}
Expand All @@ -125,36 +166,38 @@ const Filter: React.FC<FilterProps> = ({ onFilter, columnNames }) => {
</div>
);

const onSearchChange = (value: string) => {
setSearchValue(value);
onFilter(activeFilter, value);
};

return (
<Toolbar
id="attribute-search-filter-toolbar"
clearAllFilters={() => {
onSearchChange('');
setFilters([]);
setSearchValue('');
onFilter([]);
}}
>
<ToolbarContent>
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="xl">
<ToolbarItem id={`${id}-dropdown`}>{filterDropdown}</ToolbarItem>
<ToolbarGroup variant="filter-group">
<ToolbarItem>{filterDropdown}</ToolbarItem>
<ToolbarFilter
labels={searchValue !== '' ? [searchValue] : ([] as string[])}
deleteLabel={() => setSearchValue('')}
deleteLabelGroup={() => setSearchValue('')}
categoryName={activeFilter}
>
<SearchInput
placeholder={`Filter by ${activeFilter}`}
value={searchValue}
onChange={(_event, value) => onSearchChange(value)}
onClear={() => onSearchChange('')}
/>
</ToolbarFilter>
{filters.map((filter) => (
<ToolbarFilter
key={`${filter.filterName}-filter`}
labels={filter.value !== '' ? [filter.value] : ['']}
deleteLabel={() => setSearchValue('')}
deleteLabelGroup={() => onDeleteLabelGroup(filter)}
categoryName={filter.filterName}
>
{undefined}
</ToolbarFilter>
))}
</ToolbarGroup>
<SearchInput
id={`${id}-search-input`}
placeholder={`Filter by ${activeFilter.filterName}`}
value={searchValue}
onChange={(_event, value) => onSearchChange(value)}
onClear={() => onSearchChange('')}
/>
</ToolbarToggleGroup>
<Button variant="primary" ouiaId="Primary">
Create Workspace
Expand Down
Loading

0 comments on commit 0ad2045

Please sign in to comment.