diff --git a/frontend/src/__tests__/cypress/cypress/e2e/home/home.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/home/home.cy.ts index f5f7f58145..9f6ed85662 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/home/home.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/home/home.cy.ts @@ -1,29 +1,27 @@ import { enabledPage } from '~/__tests__/cypress/cypress/pages/enabled'; -import { initHomeIntercepts } from '~/__tests__/cypress/cypress/e2e/home/homeUtils'; import { mockComponents } from '~/__mocks__/mockComponents'; +import { homePage } from '~/__tests__/cypress/cypress/pages/home'; describe('Home page', () => { it('should not be shown by default', () => { - initHomeIntercepts(); + homePage.initHomeIntercepts(); cy.visit('/'); cy.findByTestId('app-page-title').should('have.text', 'Enabled'); }); it('should be shown when enabled', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); - cy.findByTestId('home-page').should('be.visible'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); // enabled applications page is still navigable cy.interceptOdh('GET /api/components', { query: { installed: 'true' } }, mockComponents()); enabledPage.visit(true); }); it('should show the home page hint', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); cy.interceptOdh('GET /api/components', { query: { installed: 'true' } }, mockComponents()); - cy.visit('/'); - cy.findByTestId('home-page-hint').should('be.visible'); + homePage.visit(); cy.findByTestId('jupyter-hint-icon').should('be.visible'); cy.findByTestId('hint-body-text').should('contain', 'Jupyter'); @@ -34,7 +32,7 @@ describe('Home page', () => { cy.findByTestId('enabled-application').should('be.visible'); }); it('should hide the home page hint when the notebook controller is disabled.', () => { - initHomeIntercepts({ disableHome: false, disableNotebookController: true }); + homePage.initHomeIntercepts({ disableHome: false, disableNotebookController: true }); cy.interceptOdh('GET /api/components', { query: { installed: 'true' } }, mockComponents()); cy.visit('/'); @@ -42,11 +40,10 @@ describe('Home page', () => { cy.findByTestId('home-page-hint').should('not.exist'); }); it('should hide the home page hint when closed', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); cy.interceptOdh('GET /api/components', { query: { installed: 'true' } }, mockComponents()); - cy.visit('/'); - cy.findByTestId('home-page-hint').should('be.visible'); + homePage.visit(); // enabled applications page is still navigable cy.findByTestId('home-page-hint-close').click(); @@ -56,7 +53,7 @@ describe('Home page', () => { cy.visit('/enabled'); cy.findByTestId('enabled-application').should('be.visible'); - cy.visit('/'); + homePage.visit(); cy.findByTestId('home-page-hint').should('not.exist'); }); }); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/home/homeAIFlows.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/home/homeAIFlows.cy.ts index f786e03311..7e224dfc4e 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/home/homeAIFlows.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/home/homeAIFlows.cy.ts @@ -1,90 +1,138 @@ -import { initHomeIntercepts } from '~/__tests__/cypress/cypress/e2e/home/homeUtils'; +import { homePage } from '~/__tests__/cypress/cypress/pages/home'; describe('Home page AI Flows', () => { it('should show the appropriate AI flow cards', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); cy.findByTestId('ai-flow-projects-card').should('be.visible'); cy.findByTestId('ai-flow-train-card').should('be.visible'); cy.findByTestId('ai-flow-models-card').should('be.visible'); }); it('should show the appropriate info cards', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); - cy.findByTestId('ai-flow-projects-card').click(); + homePage.getProjectsFlowCard().click(); cy.findByTestId('ai-flows-projects-info').should('be.visible'); cy.findByTestId('ai-flows-connections-info').should('be.visible'); cy.findByTestId('ai-flows-storage-info').should('be.visible'); - cy.findByTestId('ai-flow-train-card').click(); + homePage.getTrainFlowCard().click(); cy.findByTestId('ai-flows-workbenches-info').should('be.visible'); cy.findByTestId('ai-flows-pipelines-info').should('be.visible'); cy.findByTestId('ai-flows-runs-info').should('be.visible'); - cy.findByTestId('ai-flow-models-card').click(); + homePage.getModelsFlowCard().click(); cy.findByTestId('ai-flows-model-servers-info').should('be.visible'); cy.findByTestId('ai-flows-model-deploy-info').should('be.visible'); }); it('should close the info cards on re-click', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); - cy.findByTestId('ai-flow-projects-card').click(); + homePage.getProjectsFlowCard().click(); cy.findByTestId('ai-flows-projects-info').should('be.visible'); cy.findByTestId('ai-flows-connections-info').should('be.visible'); cy.findByTestId('ai-flows-storage-info').should('be.visible'); - cy.findByTestId('ai-flow-projects-card').click(); + homePage.getProjectsFlowCard().click(); cy.findByTestId('ai-flows-projects-info').should('not.exist'); cy.findByTestId('ai-flows-connections-info').should('not.exist'); cy.findByTestId('ai-flows-storage-info').should('not.exist'); }); it('should close the info cards on close button click', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); - cy.findByTestId('ai-flow-projects-card').click(); + homePage.getProjectsFlowCard().click(); cy.findByTestId('ai-flows-projects-info').should('be.visible'); cy.findByTestId('ai-flows-connections-info').should('be.visible'); cy.findByTestId('ai-flows-storage-info').should('be.visible'); - cy.findByTestId('ai-flows-close-info').click(); + homePage.clickAIFlowClose(); cy.findByTestId('ai-flows-projects-info').should('not.exist'); cy.findByTestId('ai-flows-connections-info').should('not.exist'); cy.findByTestId('ai-flows-storage-info').should('not.exist'); }); it('should hide sections that are disabled', () => { - initHomeIntercepts({ + homePage.initHomeIntercepts({ disableHome: false, disableProjects: true, }); - cy.visit('/'); - cy.findByTestId('home-page').should('be.visible'); + homePage.visit(); - cy.findByTestId('ai-flow-projects-card').should('not.exist'); + homePage.getProjectsFlowCard().should('not.exist'); - initHomeIntercepts({ + homePage.initHomeIntercepts({ disableHome: false, disableModelServing: true, }); - cy.visit('/'); - cy.findByTestId('home-page').should('be.visible'); - cy.findByTestId('ai-flow-models-card').should('not.exist'); + homePage.visit(); + + homePage.getModelsFlowCard().should('not.exist'); }); it('should hide info cards that are disabled', () => { - initHomeIntercepts({ + homePage.initHomeIntercepts({ disableHome: false, disablePipelines: true, }); - cy.visit('/'); - cy.findByTestId('home-page').should('be.visible'); + homePage.visit(); - cy.findByTestId('ai-flow-train-card').click(); + homePage.getTrainFlowCard().click(); cy.findByTestId('ai-flows-workbenches-info').should('be.visible'); cy.findByTestId('ai-flows-pipelines-info').should('not.exist'); cy.findByTestId('ai-flows-runs-info').should('not.exist'); }); + it('should render projects content specific to feature availability', () => { + homePage.initHomeIntercepts({ + disableHome: false, + }); + homePage.visit(); + + homePage.getProjectsFlowCard().click(); + cy.findByTestId('project-workbenches--trailer-model-mesh').scrollIntoView(); + + homePage.initHomeIntercepts({ + disableHome: false, + disableModelMesh: true, + }); + homePage.visit(); + homePage.getProjectsFlowCard().click(); + cy.findByTestId('project-workbenches--trailer-no-model-mesh').scrollIntoView(); + + homePage.initHomeIntercepts({ + disableHome: false, + disableModelServing: true, + }); + homePage.visit(); + homePage.getProjectsFlowCard().click(); + cy.findByTestId('project-workbenches--trailer-no-model-serving').scrollIntoView(); + + homePage.initHomeIntercepts({ + disableHome: false, + disablePipelines: true, + }); + homePage.visit(); + homePage.getProjectsFlowCard().click(); + cy.findByTestId('project-workbenches--trailer-no-pipelines').scrollIntoView(); + }); + it('should render workbenches content specific to feature availability', () => { + homePage.initHomeIntercepts({ + disableHome: false, + }); + homePage.visit(); + + homePage.getTrainFlowCard().click(); + cy.findByTestId('create-and-train-pipelines-trailer').scrollIntoView(); + + homePage.initHomeIntercepts({ + disableHome: false, + disablePipelines: true, + }); + homePage.visit(); + homePage.getTrainFlowCard().click(); + cy.findByTestId('create-and-train-no-pipelines-trailer').scrollIntoView(); + }); }); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/home/homeAdmin.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/home/homeAdmin.cy.ts index 3809a6f9b6..3ed4bc2b1a 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/home/homeAdmin.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/home/homeAdmin.cy.ts @@ -1,10 +1,10 @@ -import { initHomeIntercepts } from '~/__tests__/cypress/cypress/e2e/home/homeUtils'; import { mockDocs } from '~/__mocks__/mockDocs'; import { mockComponents } from '~/__mocks__/mockComponents'; import { mockQuickStarts } from '~/__mocks__/mockQuickStarts'; import { customServingRuntimesIntercept } from '~/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils'; import { notebookImageSettings } from '~/__tests__/cypress/cypress/pages/notebookImageSettings'; import { asProductAdminUser, asProjectEditUser } from '~/__tests__/cypress/cypress/utils/users'; +import { homePage } from '~/__tests__/cypress/cypress/pages/home'; describe('Home page Admin section', () => { beforeEach(() => { @@ -14,9 +14,8 @@ describe('Home page Admin section', () => { }); it('should show the admin section for admins', () => { asProductAdminUser(); - initHomeIntercepts({ disableHome: false }); - - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); cy.findByTestId('landing-page-admin').scrollIntoView(); cy.findByTestId('landing-page-admin--notebook-images').should('be.visible'); @@ -25,16 +24,16 @@ describe('Home page Admin section', () => { cy.findByTestId('landing-page-admin--user-management').should('be.visible'); }); it('should hide the admin section for non-admin users', () => { - initHomeIntercepts({ disableHome: false }); asProjectEditUser(); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); cy.findByTestId('landing-page-admin').should('not.exist'); }); it('should hide notebook images card when not available', () => { asProductAdminUser(); - initHomeIntercepts({ disableHome: false, disableBYONImageStream: true }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false, disableBYONImageStream: true }); + homePage.visit(); cy.findByTestId('landing-page-admin').scrollIntoView(); cy.findByTestId('landing-page-admin--notebook-images').should('not.exist'); @@ -44,8 +43,8 @@ describe('Home page Admin section', () => { }); it('should hide serving runtimes card when not available', () => { asProductAdminUser(); - initHomeIntercepts({ disableHome: false, disableCustomServingRuntimes: true }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false, disableCustomServingRuntimes: true }); + homePage.visit(); cy.findByTestId('landing-page-admin').scrollIntoView(); cy.findByTestId('landing-page-admin--notebook-images').should('be.visible'); @@ -55,8 +54,8 @@ describe('Home page Admin section', () => { }); it('should hide cluster settings card when not available', () => { asProductAdminUser(); - initHomeIntercepts({ disableHome: false, disableClusterManager: true }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false, disableClusterManager: true }); + homePage.visit(); cy.findByTestId('landing-page-admin').scrollIntoView(); cy.findByTestId('landing-page-admin--notebook-images').should('be.visible'); @@ -66,8 +65,8 @@ describe('Home page Admin section', () => { }); it('should hide user management card when not available', () => { asProductAdminUser(); - initHomeIntercepts({ disableHome: false, disableUserManagement: true }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false, disableUserManagement: true }); + homePage.visit(); cy.findByTestId('landing-page-admin').scrollIntoView(); cy.findByTestId('landing-page-admin--notebook-images').should('be.visible'); @@ -77,7 +76,7 @@ describe('Home page Admin section', () => { }); it('should hide the admin section if all cards are hidden', () => { asProductAdminUser(); - initHomeIntercepts({ + homePage.initHomeIntercepts({ disableHome: false, disableBYONImageStream: true, disableCustomServingRuntimes: true, @@ -85,16 +84,16 @@ describe('Home page Admin section', () => { disableUserManagement: true, }); - cy.visit('/'); + homePage.visit(); cy.get('#dashboard-page-main').find('[class="pf-v5-c-drawer__content"]').scrollTo('bottom'); cy.findByTestId('landing-page-admin').should('not.exist'); }); it('should navigate to the correct section when the title is clicked', () => { asProductAdminUser(); - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); customServingRuntimesIntercept(); - cy.visit('/'); + homePage.visit(); cy.findByTestId('landing-page-admin').scrollIntoView(); @@ -104,15 +103,15 @@ describe('Home page Admin section', () => { // Verify the Settings nav menu is now expanded notebookImageSettings.findNavItem().should('be.visible'); - cy.visit('/'); + homePage.visit(); cy.findByTestId('landing-page-admin--serving-runtimes-button').click(); cy.findByTestId('app-page-title').should('have.text', 'Serving runtimes'); - cy.visit('/'); + homePage.visit(); cy.findByTestId('landing-page-admin--cluster-settings-button').click(); cy.findByTestId('app-page-title').should('have.text', 'Cluster settings'); - cy.visit('/'); + homePage.visit(); cy.findByTestId('landing-page-admin--user-management-button').click(); cy.findByTestId('app-page-title').should('have.text', 'User management'); }); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/home/homeProjects.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/home/homeProjects.cy.ts index ce07228fa4..d765a940a0 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/home/homeProjects.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/home/homeProjects.cy.ts @@ -1,4 +1,3 @@ -import { initHomeIntercepts } from '~/__tests__/cypress/cypress/e2e/home/homeUtils'; import { mockSelfSubjectAccessReview } from '~/__mocks__/mockSelfSubjectAccessReview'; import { createProjectModal, @@ -10,6 +9,7 @@ import { SelfSubjectAccessReviewModel, } from '~/__tests__/cypress/cypress/utils/models'; import { mockProjectsK8sList } from '~/__mocks__'; +import { homePage } from '~/__tests__/cypress/cypress/pages/home'; const interceptAccessReview = (allowed: boolean) => { cy.interceptK8s( @@ -21,20 +21,20 @@ const interceptAccessReview = (allowed: boolean) => { describe('Home page Projects section', () => { it('should hide the projects section when disabled', () => { - initHomeIntercepts({ disableHome: false, disableProjects: true }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false, disableProjects: true }); + homePage.visit(); cy.findByTestId('landing-page-projects').should('not.exist'); }); it('should show the empty state w/ create button when privileged', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); cy.findByTestId('landing-page-projects-empty').should('be.visible'); }); it('should show allow project creation from the empty state when privileged', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); cy.findByTestId('landing-page-projects-empty').should('be.visible'); cy.findByTestId('create-project-button').click(); @@ -43,32 +43,32 @@ describe('Home page Projects section', () => { createProjectModal.shouldBeOpen(false); }); it('should show not allow project creation from the empty state when not privileged', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); interceptAccessReview(false); - cy.visit('/'); + homePage.visit(); cy.findByTestId('landing-page-projects-empty').should('be.visible'); cy.findByTestId('create-project-button').should('not.exist'); }); it('should show create project button when more projects exist', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); const projectsMock = mockProjectsK8sList(); cy.interceptK8sList(ProjectModel, projectsMock); - cy.visit('/'); + homePage.visit(); cy.findByTestId('create-project').should('be.visible'); cy.findByTestId('create-project-card').should('not.exist'); }); it('should not show create project button when more projects exist but user is not allowed', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); interceptAccessReview(false); const projectsMock = mockProjectsK8sList(); cy.interceptK8sList(ProjectModel, projectsMock); - cy.visit('/'); + homePage.visit(); cy.findByTestId('create-project').should('not.exist'); cy.findByTestId('create-project-card').should('not.exist'); @@ -76,20 +76,20 @@ describe('Home page Projects section', () => { cy.findByTestId('request-project-card').should('not.exist'); }); it('should show create project card when no more projects exist', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); const projectsMock = mockProjectsK8sList(); const projects = projectsMock.items; projectsMock.items = projects.slice(0, 2); cy.interceptK8sList(ProjectModel, projectsMock); - cy.visit('/'); + homePage.visit(); cy.findByTestId('create-project').should('not.exist'); cy.findByTestId('create-project-card').should('be.visible'); }); it('should show a request project card when no more projects exist but user is not allowed', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); interceptAccessReview(false); const projectsMock = mockProjectsK8sList(); const projects = projectsMock.items; @@ -97,7 +97,7 @@ describe('Home page Projects section', () => { cy.interceptK8sList(ProjectModel, projectsMock); - cy.visit('/'); + homePage.visit(); cy.findByTestId('create-project').should('not.exist'); cy.findByTestId('create-project-card').should('not.exist'); @@ -105,7 +105,7 @@ describe('Home page Projects section', () => { cy.findByTestId('request-project-help').should('not.exist'); }); it('should navigate to the project when the name is clicked', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); interceptAccessReview(false); const projectsMock = mockProjectsK8sList(); const projects = projectsMock.items; @@ -113,14 +113,14 @@ describe('Home page Projects section', () => { cy.interceptK8sList(ProjectModel, projectsMock); - cy.visit('/'); + homePage.visit(); cy.findByTestId(`project-link-${projects[0].metadata.name}`).click(); cy.url().should('include', projects[0].metadata.name); projectDetails.findComponent('overview').should('be.visible'); }); it('should navigate to the project list', () => { - initHomeIntercepts({ disableHome: false }); + homePage.initHomeIntercepts({ disableHome: false }); interceptAccessReview(false); const projectsMock = mockProjectsK8sList(); const projects = projectsMock.items; @@ -128,7 +128,7 @@ describe('Home page Projects section', () => { cy.interceptK8sList(ProjectModel, projectsMock); - cy.visit('/'); + homePage.visit(); cy.findByTestId('goto-projects-link').click(); projectListPage.findProjectsTable().should('be.visible'); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/home/homeResources.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/home/homeResources.cy.ts index f75734b188..8a0211244e 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/home/homeResources.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/home/homeResources.cy.ts @@ -1,35 +1,30 @@ -import { initHomeIntercepts } from '~/__tests__/cypress/cypress/e2e/home/homeUtils'; import { mockDocs } from '~/__mocks__/mockDocs'; import { mockComponents } from '~/__mocks__/mockComponents'; import { mockQuickStarts } from '~/__mocks__/mockQuickStarts'; +import { homePage } from '~/__tests__/cypress/cypress/pages/home'; describe('Home page Resources section', () => { beforeEach(() => { cy.interceptOdh('GET /api/docs', mockDocs()); cy.interceptOdh('GET /api/components', null, mockComponents()); cy.interceptOdh('GET /api/quickstarts', mockQuickStarts()); - - cy.visit('/'); + homePage.initHomeIntercepts({ disableHome: false }); + homePage.visit(); }); it('should show the resources section', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); - cy.findByTestId('landing-page-resources').scrollIntoView(); cy.findByTestId('resource-card-create-jupyter-notebook').should('be.visible'); }); it('should hide the the resource section if none are available', () => { cy.interceptOdh('GET /api/quickstarts', []); - - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.visit(); cy.findByTestId('landing-page-resources').should('not.exist'); }); it('should navigate to the resources page', () => { - initHomeIntercepts({ disableHome: false }); - cy.visit('/'); + homePage.visit(); + cy.findByTestId('goto-resources-link').scrollIntoView(); cy.findByTestId('goto-resources-link').click(); cy.findByTestId('app-page-title').should('have.text', 'Resources'); }); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/home/homeUtils.ts b/frontend/src/__tests__/cypress/cypress/e2e/home/homeUtils.ts deleted file mode 100644 index 48e2458c16..0000000000 --- a/frontend/src/__tests__/cypress/cypress/e2e/home/homeUtils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { mockDashboardConfig } from '~/__mocks__'; - -export const initHomeIntercepts = ( - config: Parameters[0] = {}, -): void => { - cy.interceptOdh('GET /api/config', mockDashboardConfig(config)); -}; diff --git a/frontend/src/__tests__/cypress/cypress/pages/home.ts b/frontend/src/__tests__/cypress/cypress/pages/home.ts new file mode 100644 index 0000000000..4d0626c00a --- /dev/null +++ b/frontend/src/__tests__/cypress/cypress/pages/home.ts @@ -0,0 +1,35 @@ +import { mockDashboardConfig } from '~/__mocks__'; + +class HomePage { + visit() { + cy.visit('/'); + this.wait(); + } + + private wait() { + cy.findByTestId('home-page').should('be.visible'); + cy.testA11y(); + } + + initHomeIntercepts(config: Parameters[0] = {}) { + cy.interceptOdh('GET /api/config', mockDashboardConfig(config)); + } + + getProjectsFlowCard() { + return cy.findByTestId('ai-flow-projects-card'); + } + + getTrainFlowCard() { + return cy.findByTestId('ai-flow-train-card'); + } + + getModelsFlowCard() { + return cy.findByTestId('ai-flow-models-card'); + } + + clickAIFlowClose() { + cy.findByTestId('ai-flows-close-info').click(); + } +} + +export const homePage = new HomePage(); diff --git a/frontend/src/concepts/design/CollapsibleSection.tsx b/frontend/src/concepts/design/CollapsibleSection.tsx index 180183ca2d..097ec2348b 100644 --- a/frontend/src/concepts/design/CollapsibleSection.tsx +++ b/frontend/src/concepts/design/CollapsibleSection.tsx @@ -22,7 +22,7 @@ const CollapsibleSection: React.FC = ({ showChildrenWhenClosed, }) => { const [innerOpen, setInnerOpen] = React.useState(true); - const localId = id || title.replace(' ', '-'); + const localId = id || title.replace(/ /g, '-'); const titleId = `${localId}-title`; return ( diff --git a/frontend/src/concepts/design/InfoGalleryItem.tsx b/frontend/src/concepts/design/InfoGalleryItem.tsx index 7d396ec889..e2a4ab72cd 100644 --- a/frontend/src/concepts/design/InfoGalleryItem.tsx +++ b/frontend/src/concepts/design/InfoGalleryItem.tsx @@ -7,8 +7,6 @@ import { GalleryItemProps, Stack, StackItem, - Text, - TextContent, } from '@patternfly/react-core'; import { SectionType, sectionTypeBackgroundColor } from '~/concepts/design/utils'; import DividedGalleryItem from '~/concepts/design/DividedGalleryItem'; @@ -20,7 +18,7 @@ type InfoGalleryItemProps = { title: string; imgSrc: string; sectionType: SectionType; - description: string; + description: React.ReactNode; isOpen: boolean; onClick?: () => void; testId?: string; @@ -79,13 +77,7 @@ const InfoGalleryItem: React.FC = ({ )} - {isOpen ? ( - - - {description} - - - ) : null} + {isOpen ? {description} : null} ); diff --git a/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx b/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx index 020fbf7d37..018a14e2e1 100644 --- a/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx +++ b/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Text, TextContent } from '@patternfly/react-core'; import { ProjectObjectType, SectionType, typedObjectImage } from '~/concepts/design/utils'; import InfoGalleryItem from '~/concepts/design/InfoGalleryItem'; import { SupportedArea } from '~/concepts/areas'; @@ -19,7 +20,25 @@ const CreateAndTrainGallery: React.FC<{ onClose: () => void }> = ({ onClose }) = title="Workbenches" imgSrc={typedObjectImage(ProjectObjectType.notebook)} sectionType={SectionType.training} - description="A workbench is an isolated area where you can work with models in your preferred IDE, such as a Jupyter notebook. You can add accelerators and data connections, create pipelines, and add cluster storage in your workbench." + description={ + + + A workbench is an instance of your development and experimentation environment. + Specify your preferred IDE for model development and training, such as Jupyter + Notebook; connect to data sources; add persistent storage for data retention;{' '} + {pipelinesAvailable ? ( + + assign accelerators to optimize performance; and create pipelines to automate + machine learning workflows. + + ) : ( + + and assign accelerators to optimize performance. + + )} + + + } isOpen />, ); @@ -33,7 +52,14 @@ const CreateAndTrainGallery: React.FC<{ onClose: () => void }> = ({ onClose }) = title="Pipelines" imgSrc={typedObjectImage(ProjectObjectType.pipeline)} sectionType={SectionType.training} - description="Pipelines are platforms for building and deploying portable and scalable machine-learning (ML) workflows." + description={ + + + Pipelines streamline and automate your machine learning workflows, enabling you to + manage and reproduce complex tasks. + + + } isOpen />, void }> = ({ onClose }) = title="Runs" imgSrc={typedObjectImage(ProjectObjectType.pipelineRun)} sectionType={SectionType.training} - description="Runs represent a single execution of a pipeline. Runs enable you to test your pipelines by executing each step of a pipeline until complete, or until a failure occurs." + description={ + + + A run represents a single execution of all steps in a pipeline until it is complete, + or until a failure occurs. + + + } isOpen />, ); diff --git a/frontend/src/pages/home/aiFlows/DeployAndMonitorGallery.tsx b/frontend/src/pages/home/aiFlows/DeployAndMonitorGallery.tsx index 2a09f03598..45da98f2b1 100644 --- a/frontend/src/pages/home/aiFlows/DeployAndMonitorGallery.tsx +++ b/frontend/src/pages/home/aiFlows/DeployAndMonitorGallery.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Text, TextContent } from '@patternfly/react-core'; import { ProjectObjectType, SectionType, typedObjectImage } from '~/concepts/design/utils'; import InfoGalleryItem from '~/concepts/design/InfoGalleryItem'; import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses'; @@ -18,7 +19,16 @@ const DeployAndMonitorGallery: React.FC<{ onClose: () => void }> = ({ onClose }) title="Model servers" imgSrc={typedObjectImage(ProjectObjectType.modelServer)} sectionType={SectionType.serving} - description="Model servers are used to deploy models and to allow apps to send requests to your models. Configuring a model server includes specifying the number of replicas being deployed, the server size, the token authorization, the serving runtime, and how the project that the model server belongs to is accessed." + description={ + + + Use model servers to deploy models for testing and implementing in intelligent + applications. Configuring a model server includes specifying the number of replicas + being deployed, the server size, the token authorization, the serving runtime, and how + the project that the model server belongs to is accessed. + + + } isOpen />, ); @@ -31,7 +41,14 @@ const DeployAndMonitorGallery: React.FC<{ onClose: () => void }> = ({ onClose }) title="Deploying models" imgSrc={typedObjectImage(ProjectObjectType.deployingModels)} sectionType={SectionType.serving} - description="Deploy models to test them and integrate them into applications. Deploying a model makes it accessible via an API, enabling you to return predictions based on data inputs." + description={ + + + Deploy models to test them and integrate them into applications. Deploying a model makes + it accessible via an API, enabling you to return predictions based on data inputs. + + + } isOpen />, ); diff --git a/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx b/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx index 1ce047c019..65f26468a6 100644 --- a/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx +++ b/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { Text, TextContent } from '@patternfly/react-core'; import { ProjectObjectType, SectionType, typedObjectImage } from '~/concepts/design/utils'; import InfoGalleryItem from '~/concepts/design/InfoGalleryItem'; import { SupportedArea } from '~/concepts/areas'; @@ -8,14 +9,41 @@ import InfoGallery from './InfoGallery'; const ProjectsGallery: React.FC<{ onClose: () => void }> = ({ onClose }) => { const { status: pipelinesAvailable } = useIsAreaAvailable(SupportedArea.DS_PIPELINES); + const { status: modelServingAvailable } = useIsAreaAvailable(SupportedArea.MODEL_SERVING); const servingPlatformStatuses = useServingPlatformStatuses(); const modelMeshEnabled = servingPlatformStatuses.modelMesh.enabled; - const projectDescription = `Projects allow you to organize your related work in one place. You can add workbenches, ${ - pipelinesAvailable ? 'pipelines, ' : '' - }cluster storage, data connections, and model${ - modelMeshEnabled ? ' servers' : 's' - } to your project.`; + const getProjectDescriptionAdditionalText = () => { + if (pipelinesAvailable && modelServingAvailable) { + if (modelMeshEnabled) { + return ( + + In addition, the workbenches can share models and data with pipelines and model servers. + + ); + } + return ( + + In addition, the workbenches can share models and data with pipelines. + + ); + } + if (pipelinesAvailable) { + return ( + + In addition, the workbenches can share models and data with pipelines. + + ); + } + if (modelServingAvailable && modelMeshEnabled) { + return ( + + In addition, the workbenches can share models and data with model servers. + + ); + } + return null; + }; const infoItems = [ void }> = ({ onClose }) => { title="Projects" imgSrc={typedObjectImage(ProjectObjectType.project)} sectionType={SectionType.organize} - description={projectDescription} + description={ + + + Projects allow you and your team to organize and collaborate on resources within + separate namespaces. + + + Within a project, you can create multiple workbenches, each with their own IDE, data + connections, and cluster storage. {getProjectDescriptionAdditionalText()} + + + } isOpen />, void }> = ({ onClose }) => { title="Data connections" imgSrc={typedObjectImage(ProjectObjectType.dataConnection)} sectionType={SectionType.organize} - description="You can add data connections to workbenches to connect your project to data inputs and object storage buckets. You can also use data connections to specify the location of your models during deployment." + description={ + + + You can add data connections to link your project and its workbenches to data sources, + and to object storage buckets which save data and models that you want to deploy. + + + } isOpen />, void }> = ({ onClose }) => { title="Cluster storage" imgSrc={typedObjectImage(ProjectObjectType.clusterStorage)} sectionType={SectionType.organize} - description="Cluster storage can be added to a workbench to save your project’s data on a selected cluster." + description={ + + + Add cluster storage to a workbench for saving your project’s data to your cluster. + + + } isOpen />, ]; diff --git a/frontend/src/pages/home/useEnableTeamSection.tsx b/frontend/src/pages/home/useEnableTeamSection.tsx index 20f4bf72b5..09b8f443e1 100644 --- a/frontend/src/pages/home/useEnableTeamSection.tsx +++ b/frontend/src/pages/home/useEnableTeamSection.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { PageSection, TextVariants } from '@patternfly/react-core'; +import { PageSection, Text, TextContent, TextVariants } from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; import CollapsibleSection from '~/concepts/design/CollapsibleSection'; import { SectionType, sectionTypeBorderColor } from '~/concepts/design/utils'; @@ -44,8 +44,14 @@ export const useEnableTeamSection = (): React.ReactNode => { onClick={() => navigate('/notebookImages')} imgSrc={notebookImagesImage} sectionType={SectionType.setup} - description="These are instances of your development and experimentation environment. They - typically contain IDEs, such as JupyterLab, RStudio, and Visual Studio Code." + description={ + + + These are instances of your development and experimentation environment. They + typically contain IDEs, such as JupyterLab, RStudio, and Visual Studio Code. + + + } />, ); } @@ -59,9 +65,15 @@ export const useEnableTeamSection = (): React.ReactNode => { onClick={() => navigate('/servingRuntimes')} imgSrc={servingRuntimesImage} sectionType={SectionType.setup} - description="Administrators can access notebook servers that are owned by other users to - correct configuration errors or help a data scientist troubleshoot problems with their - environment." + description={ + + + Administrators can access notebook servers that are owned by other users to correct + configuration errors or help a data scientist troubleshoot problems with their + environment. + + + } />, ); } @@ -75,8 +87,14 @@ export const useEnableTeamSection = (): React.ReactNode => { onClick={() => navigate('/clusterSettings')} imgSrc={clusterSettingsImage} sectionType={SectionType.setup} - description="You can change the default size of the cluster’s persistent volume claim (PVC) - ensuring that the storage requested matches your common storage workflow." + description={ + + + You can change the default size of the cluster’s persistent volume claim (PVC) + ensuring that the storage requested matches your common storage workflow. + + + } />, ); } @@ -90,9 +108,15 @@ export const useEnableTeamSection = (): React.ReactNode => { onClick={() => navigate('/groupSettings')} imgSrc={userImage} sectionType={SectionType.setup} - description="If you plan to restrict access to your instance by defining specialized user - groups, you must grant users permission access by adding user accounts to the Red Hat - OpenShift AI user group, administrator group, or both." + description={ + + + If you plan to restrict access to your instance by defining specialized user groups, + you must grant users permission access by adding user accounts to the Red Hat + OpenShift AI user group, administrator group, or both. + + + } />, ); } diff --git a/frontend/src/pages/projects/screens/detail/overview/configuration/ConfigurationSection.tsx b/frontend/src/pages/projects/screens/detail/overview/configuration/ConfigurationSection.tsx index bc8707cf79..9b4e6e803b 100644 --- a/frontend/src/pages/projects/screens/detail/overview/configuration/ConfigurationSection.tsx +++ b/frontend/src/pages/projects/screens/detail/overview/configuration/ConfigurationSection.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { useNavigate } from 'react-router-dom'; +import { Text, TextContent } from '@patternfly/react-core'; import { useAccessReview } from '~/api'; import { AccessReviewResourceAttributes } from '~/k8sTypes'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; @@ -53,7 +54,14 @@ const ConfigurationSection: React.FC = () => { sectionType={SectionType.setup} imgSrc={typedObjectImage(ProjectObjectType.clusterStorage)} title="Cluster storage" - description="To save your project data, you can add cluster storage and optionally connect the storage to a workbench." + description={ + + + To save your project data, you can add cluster storage and optionally connect the + storage to a workbench. + + + } isOpen={open} onClick={() => navigate( @@ -65,7 +73,15 @@ const ConfigurationSection: React.FC = () => { sectionType={SectionType.setup} imgSrc={typedObjectImage(ProjectObjectType.dataConnection)} title="Data connections" - description="You can add data connections to workbenches to connect your project to data inputs and object storage buckets. You can also use data connections to specify the location of your models during deployment." + description={ + + + You can add data connections to workbenches to connect your project to data inputs + and object storage buckets. You can also use data connections to specify the + location of your models during deployment. + + + } isOpen={open} onClick={() => navigate( @@ -78,7 +94,11 @@ const ConfigurationSection: React.FC = () => { sectionType={SectionType.setup} title="Permissions" imgSrc={typedObjectImage(ProjectObjectType.group)} - description="Add users and groups to share access to your project." + description={ + + Add users and groups to share access to your project. + + } isOpen={open} onClick={() => navigate(