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..41c8a88795 100644 --- a/frontend/src/concepts/design/InfoGalleryItem.tsx +++ b/frontend/src/concepts/design/InfoGalleryItem.tsx @@ -20,7 +20,7 @@ type InfoGalleryItemProps = { title: string; imgSrc: string; sectionType: SectionType; - description: string; + description: React.ReactNode; isOpen: boolean; onClick?: () => void; testId?: string; diff --git a/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx b/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx index 020fbf7d37..5fa0931ecd 100644 --- a/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx +++ b/frontend/src/pages/home/aiFlows/CreateAndTrainGallery.tsx @@ -11,6 +11,17 @@ const CreateAndTrainGallery: React.FC<{ onClose: () => void }> = ({ onClose }) = const infoItems = []; + const workbenchDescriptionTrailer = pipelinesAvailable ? ( + + assign accelerators to optimize performance; and create pipelines to automate machine learning + workflows. + + ) : ( + + and assign accelerators to optimize performance. + + ); + if (workbenchesAvailable) { infoItems.push( 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;{' '} + {workbenchDescriptionTrailer} + + } isOpen />, ); @@ -33,7 +51,7 @@ 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..ad05a94593 100644 --- a/frontend/src/pages/home/aiFlows/DeployAndMonitorGallery.tsx +++ b/frontend/src/pages/home/aiFlows/DeployAndMonitorGallery.tsx @@ -18,7 +18,7 @@ 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 />, ); diff --git a/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx b/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx index 1ce047c019..b0abc0b375 100644 --- a/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx +++ b/frontend/src/pages/home/aiFlows/ProjectsGallery.tsx @@ -6,16 +6,46 @@ import useIsAreaAvailable from '~/concepts/areas/useIsAreaAvailable'; import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses'; import InfoGallery from './InfoGallery'; +const projectDescriptionLeadText = + 'Projects allow you and your team to organize and collaborate on resources within separate namespaces.'; + 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={ + + {projectDescriptionLeadText} +
+
+ 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 />, ];