From d93405716dfc2941c42861cc4663659e3272599d Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Date: Mon, 9 Sep 2024 16:13:14 -0400 Subject: [PATCH] Updates for projects view filter toolbar --- .../cypress/pages/components/TableToolbar.ts | 2 +- .../cypress/cypress/pages/projects.ts | 11 ++- .../tests/mocked/projects/projectList.cy.ts | 8 +- .../src/components/PopoverListContent.tsx | 41 ++++++++ .../screens/projects/EmptyProjects.tsx | 21 ++-- .../screens/projects/ProjectListView.tsx | 96 +++++++++---------- .../screens/projects/ProjectsToolbar.tsx | 96 +++++++++++++++++++ .../pages/projects/screens/projects/const.ts | 22 +++++ 8 files changed, 221 insertions(+), 76 deletions(-) create mode 100644 frontend/src/components/PopoverListContent.tsx create mode 100644 frontend/src/pages/projects/screens/projects/ProjectsToolbar.tsx create mode 100644 frontend/src/pages/projects/screens/projects/const.ts diff --git a/frontend/src/__tests__/cypress/cypress/pages/components/TableToolbar.ts b/frontend/src/__tests__/cypress/cypress/pages/components/TableToolbar.ts index 53bfd3aded..a005266550 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/components/TableToolbar.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/components/TableToolbar.ts @@ -6,7 +6,7 @@ export class TableToolbar extends Contextual { } findFilterMenuOption(id: string, name: string): Cypress.Chainable> { - return this.findToggleButton(id).parents().findByRole('option', { name }); + return this.findToggleButton(id).parents().findByRole('menuitem', { name }); } findSearchInput(): Cypress.Chainable> { diff --git a/frontend/src/__tests__/cypress/cypress/pages/projects.ts b/frontend/src/__tests__/cypress/cypress/pages/projects.ts index 4e3ecfe04d..034426e205 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/projects.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/projects.ts @@ -4,7 +4,12 @@ import { DeleteModal } from '~/__tests__/cypress/cypress/pages/components/Delete import { TableRow } from './components/table'; import { TableToolbar } from './components/TableToolbar'; -class ProjectListToolbar extends TableToolbar {} +class ProjectListToolbar extends TableToolbar { + findFilterInput(name: string): Cypress.Chainable> { + return this.find().findByLabelText(`Filter by ${name}`); + } +} + class NotebookRow extends TableRow { findNotebookImageAvailability() { return cy.findByTestId('notebook-image-availability'); @@ -89,7 +94,7 @@ class ProjectListPage { } getTableToolbar() { - return new ProjectListToolbar(() => cy.findByTestId('dashboard-table-toolbar')); + return new ProjectListToolbar(() => cy.findByTestId('projects-table-toolbar')); } findCreateWorkbenchButton() { @@ -109,7 +114,7 @@ class ProjectListPage { class CreateEditProjectModal extends Modal { constructor(private edit = false) { - super(`${edit ? 'Edit' : 'Create'} data science project`); + super(`${edit ? 'Edit' : 'Create'} project`); } findNameInput() { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/projectList.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/projectList.cy.ts index 7cee1dd9cf..bac3721ecd 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/projectList.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/projectList.cy.ts @@ -196,8 +196,8 @@ describe('Data science projects details', () => { // Select the "Name" filter const projectListToolbar = projectListPage.getTableToolbar(); - projectListToolbar.findFilterMenuOption('filter-dropdown-select', 'Name').click(); - projectListToolbar.findSearchInput().type('Test Project'); + projectListToolbar.findFilterMenuOption('filter-toolbar-dropdown', 'Name').click(); + projectListToolbar.findFilterInput('name').type('Test Project'); // Verify only rows with the typed run name exist projectListPage.getProjectRow('Test Project').find().should('exist'); }); @@ -208,8 +208,8 @@ describe('Data science projects details', () => { // Select the "User" filter const projectListToolbar = projectListPage.getTableToolbar(); - projectListToolbar.findFilterMenuOption('filter-dropdown-select', 'User').click(); - projectListToolbar.findSearchInput().type('test-user'); + projectListToolbar.findFilterMenuOption('filter-toolbar-dropdown', 'User').click(); + projectListToolbar.findFilterInput('user').type('test-user'); // Verify only rows with the typed run user exist projectListPage.getProjectRow('Test Project').find().should('exist'); }); diff --git a/frontend/src/components/PopoverListContent.tsx b/frontend/src/components/PopoverListContent.tsx new file mode 100644 index 0000000000..6526597e0f --- /dev/null +++ b/frontend/src/components/PopoverListContent.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import { + Text, + TextContent, + TextContentProps, + TextList, + TextListItem, +} from '@patternfly/react-core'; + +type PopoverListContentProps = TextContentProps & { + leadText?: React.ReactNode; + listHeading?: React.ReactNode; + listItems: React.ReactNode[]; +}; + +const ContentText: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + +); + +const PopoverListContent: React.FC = ({ + leadText, + listHeading, + listItems, + ...props +}) => ( + + {leadText ? {leadText} : null} + {listHeading ? {listHeading} : null} + + {listItems.map((item, index) => ( + + {item} + + ))} + + +); + +export default PopoverListContent; diff --git a/frontend/src/pages/projects/screens/projects/EmptyProjects.tsx b/frontend/src/pages/projects/screens/projects/EmptyProjects.tsx index 58b8f3ee01..20c52dec41 100644 --- a/frontend/src/pages/projects/screens/projects/EmptyProjects.tsx +++ b/frontend/src/pages/projects/screens/projects/EmptyProjects.tsx @@ -8,13 +8,12 @@ import { Popover, Button, Icon, - TextContent, - TextList, - TextListItem, } from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons'; +import PopoverListContent from '~/components/PopoverListContent'; import projectsEmptyStateImg from '~/images/empty-state-projects-color.svg'; +import { FindAdministratorOptions } from '~/pages/projects/screens/projects/const'; import NewProjectButton from './NewProjectButton'; type EmptyProjectsProps = { @@ -54,18 +53,10 @@ const EmptyProjects: React.FC = ({ allowCreate }) => { minWidth="400px" headerContent="Your administrator might be:" bodyContent={ - - - The person who gave you your username - - Someone in your IT department or Help desk (at a company or school) - - - The person who manages your email service or web site (in a small business or - club) - - - + } > - - } + emptyTableView={} data-testid="project-view-table" rowRenderer={(project) => ( = ({ allowCreate }) => { /> )} toolbarContent={ - <> - - - { - setSearchType(newSearchType); - }} - onSearchValueChange={(searchValue: string) => { - setSearch(searchValue); - }} - /> - - - - {allowCreate && ( - - navigate(`/projects/${projectName}`)} - /> - - )} - - + } /> void; + onClearFilters: () => void; +}; + +const ProjectsToolbar: React.FC = ({ + allowCreate, + filterData, + onFilterUpdate, + onClearFilters, +}) => { + const navigate = useNavigate(); + + return ( + + data-testid="projects-table-toolbar" + filterOptions={projectsFilterOptions} + filterOptionRenders={{ + [ProjectsFilterOptions.name]: ({ onChange, ...props }) => ( + onChange(value)} + /> + ), + [ProjectsFilterOptions.user]: ({ onChange, ...props }) => ( + onChange(value)} + /> + ), + }} + filterData={filterData} + onClearFilters={onClearFilters} + onFilterUpdate={onFilterUpdate} + > + + + {allowCreate ? ( + navigate(`/projects/${projectName}`)} + /> + ) : ( + + } + > + + + )} + + + + ); +}; + +export default ProjectsToolbar; diff --git a/frontend/src/pages/projects/screens/projects/const.ts b/frontend/src/pages/projects/screens/projects/const.ts new file mode 100644 index 0000000000..df08bb8368 --- /dev/null +++ b/frontend/src/pages/projects/screens/projects/const.ts @@ -0,0 +1,22 @@ +export enum ProjectsFilterOptions { + name = 'Name', + user = 'User', +} + +export const projectsFilterOptions = { + [ProjectsFilterOptions.name]: 'Name', + [ProjectsFilterOptions.user]: 'User', +}; + +export type ProjectsFilterDataType = Record; + +export const initialProjectsFilterData: ProjectsFilterDataType = { + [ProjectsFilterOptions.name]: '', + [ProjectsFilterOptions.user]: '', +}; + +export const FindAdministratorOptions = [ + 'The person who gave you your username', + 'Someone in your IT department or Help desk (at a company or school)', + 'The person who manages your email service or web site (in a small business or club)', +];