diff --git a/frontend/src/__mocks__/mockDashboardConfig.ts b/frontend/src/__mocks__/mockDashboardConfig.ts index ef2831e497..370e500251 100644 --- a/frontend/src/__mocks__/mockDashboardConfig.ts +++ b/frontend/src/__mocks__/mockDashboardConfig.ts @@ -1,4 +1,5 @@ import { DashboardConfigKind, KnownLabels } from '~/k8sTypes'; +import { NotebookSize } from '~/types'; type MockDashboardConfigType = { disableInfo?: boolean; @@ -26,6 +27,7 @@ type MockDashboardConfigType = { disableDistributedWorkloads?: boolean; disableModelRegistry?: boolean; disableNotebookController?: boolean; + notebookSizes?: NotebookSize[]; }; export const mockDashboardConfig = ({ @@ -54,6 +56,73 @@ export const mockDashboardConfig = ({ disableDistributedWorkloads = false, disableModelRegistry = true, disableNotebookController = false, + notebookSizes = [ + { + name: 'XSmall', + resources: { + limits: { + cpu: '0.5', + memory: '500Mi', + }, + requests: { + cpu: '0.1', + memory: '100Mi', + }, + }, + }, + { + name: 'Small', + resources: { + limits: { + cpu: '2', + memory: '8Gi', + }, + requests: { + cpu: '1', + memory: '8Gi', + }, + }, + }, + { + name: 'Medium', + resources: { + limits: { + cpu: '6', + memory: '24Gi', + }, + requests: { + cpu: '3', + memory: '24Gi', + }, + }, + }, + { + name: 'Large', + resources: { + limits: { + cpu: '14', + memory: '56Gi', + }, + requests: { + cpu: '7', + memory: '56Gi', + }, + }, + }, + { + name: 'X Large', + resources: { + limits: { + cpu: '30', + memory: '120Gi', + }, + requests: { + cpu: '15', + memory: '120Gi', + }, + }, + }, + ], }: MockDashboardConfigType): DashboardConfigKind => ({ apiVersion: 'opendatahub.io/v1alpha', kind: 'OdhDashboardConfig', @@ -147,73 +216,7 @@ export const mockDashboardConfig = ({ }, }, ], - notebookSizes: [ - { - name: 'XSmall', - resources: { - limits: { - cpu: '0.5', - memory: '500Mi', - }, - requests: { - cpu: '0.1', - memory: '100Mi', - }, - }, - }, - { - name: 'Small', - resources: { - limits: { - cpu: '2', - memory: '8Gi', - }, - requests: { - cpu: '1', - memory: '8Gi', - }, - }, - }, - { - name: 'Medium', - resources: { - limits: { - cpu: '6', - memory: '24Gi', - }, - requests: { - cpu: '3', - memory: '24Gi', - }, - }, - }, - { - name: 'Large', - resources: { - limits: { - cpu: '14', - memory: '56Gi', - }, - requests: { - cpu: '7', - memory: '56Gi', - }, - }, - }, - { - name: 'X Large', - resources: { - limits: { - cpu: '30', - memory: '120Gi', - }, - requests: { - cpu: '15', - memory: '120Gi', - }, - }, - }, - ], + notebookSizes, templateOrder: ['test-model'], templateDisablement: ['test-model'], }, diff --git a/frontend/src/__tests__/cypress/cypress/pages/workbench.ts b/frontend/src/__tests__/cypress/cypress/pages/workbench.ts index d0279617a7..cd631d6cca 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/workbench.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/workbench.ts @@ -299,6 +299,10 @@ class CreateSpawnerPage { findBucketInput() { return cy.findByTestId('field AWS_S3_BUCKET'); } + + findContainerSizeInput(name: string) { + return cy.findByTestId('container-size-group').contains(name); + } } class EditSpawnerPage extends CreateSpawnerPage { @@ -326,6 +330,10 @@ class EditSpawnerPage extends CreateSpawnerPage { cy.findByTestId('container-size-group').contains(name).should('exist'); return this; } + + findCancelButton() { + return cy.findByTestId('workbench-cancel-button'); + } } class NotFoundSpawnerPage { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/workbench.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/workbench.cy.ts index dc394967df..07cec365b8 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/workbench.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/workbench.cy.ts @@ -1,4 +1,5 @@ import { + mockDashboardConfig, mockDscStatus, mockK8sResourceList, mockNotebookK8sResource, @@ -32,14 +33,33 @@ import { SecretModel, } from '~/__tests__/cypress/cypress/utils/models'; import { mock200Status } from '~/__mocks__/mockK8sStatus'; +import type { NotebookSize } from '~/types'; const configYamlPath = '../../__mocks__/mock-upload-configmap.yaml'; type HandlersProps = { isEmpty?: boolean; + notebookSizes?: NotebookSize[]; }; -const initIntercepts = ({ isEmpty = false }: HandlersProps) => { +const initIntercepts = ({ + isEmpty = false, + notebookSizes = [ + { + name: 'Medium', + resources: { + limits: { + cpu: '6', + memory: '24Gi', + }, + requests: { + cpu: '3', + memory: '24Gi', + }, + }, + }, + ], +}: HandlersProps) => { cy.interceptOdh( 'GET /api/dsc/status', mockDscStatus({ @@ -48,6 +68,12 @@ const initIntercepts = ({ isEmpty = false }: HandlersProps) => { }, }), ); + cy.interceptOdh( + 'GET /api/config', + mockDashboardConfig({ + notebookSizes, + }), + ); cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})])); cy.interceptK8s(ProjectModel, mockProjectK8sResource({})); cy.interceptK8sList(PodModel, mockK8sResourceList([mockPodK8sResource({})])); @@ -149,7 +175,37 @@ describe('Workbench page', () => { }); it('Create workbench', () => { - initIntercepts({ isEmpty: true }); + initIntercepts({ + isEmpty: true, + notebookSizes: [ + { + name: 'XSmall', + resources: { + limits: { + cpu: '0.5', + memory: '500Mi', + }, + requests: { + cpu: '0.1', + memory: '100Mi', + }, + }, + }, + { + name: 'Small', + resources: { + limits: { + cpu: '2', + memory: '8Gi', + }, + requests: { + cpu: '1', + memory: '8Gi', + }, + }, + }, + ], + }); workbenchPage.visit('test-project'); workbenchPage.findCreateButton().click(); createSpawnerPage.findSubmitButton().should('be.disabled'); @@ -262,7 +318,37 @@ describe('Workbench page', () => { }); it('Create workbench with numbers', () => { - initIntercepts({ isEmpty: true }); + initIntercepts({ + isEmpty: true, + notebookSizes: [ + { + name: 'XSmall', + resources: { + limits: { + cpu: '0.5', + memory: '500Mi', + }, + requests: { + cpu: '0.1', + memory: '100Mi', + }, + }, + }, + { + name: 'Small', + resources: { + limits: { + cpu: '2', + memory: '8Gi', + }, + requests: { + cpu: '1', + memory: '8Gi', + }, + }, + }, + ], + }); workbenchPage.visit('test-project'); workbenchPage.findCreateButton().click(); createSpawnerPage.findSubmitButton().should('be.disabled'); @@ -300,7 +386,36 @@ describe('Workbench page', () => { }); it('list workbench and table sorting', () => { - initIntercepts({}); + initIntercepts({ + notebookSizes: [ + { + name: 'XSmall', + resources: { + limits: { + cpu: '0.5', + memory: '500Mi', + }, + requests: { + cpu: '0.1', + memory: '100Mi', + }, + }, + }, + { + name: 'Small', + resources: { + limits: { + cpu: '2', + memory: '8Gi', + }, + requests: { + cpu: '1', + memory: '8Gi', + }, + }, + }, + ], + }); workbenchPage.visit('test-project'); const notebookRow = workbenchPage.getNotebookRow('Test Notebook'); notebookRow.shouldHaveNotebookImageName('Test Image'); @@ -397,7 +512,36 @@ describe('Workbench page', () => { }); it('Edit workbench', () => { - initIntercepts({}); + initIntercepts({ + notebookSizes: [ + { + name: 'XSmall', + resources: { + limits: { + cpu: '0.5', + memory: '500Mi', + }, + requests: { + cpu: '0.1', + memory: '100Mi', + }, + }, + }, + { + name: 'Small', + resources: { + limits: { + cpu: '2', + memory: '8Gi', + }, + requests: { + cpu: '1', + memory: '8Gi', + }, + }, + }, + ], + }); editSpawnerPage.visit('test-notebook'); editSpawnerPage.findNameInput().should('have.value', 'Test Notebook'); editSpawnerPage.shouldHaveNotebookImageSelectInput('Test Image'); @@ -437,10 +581,41 @@ describe('Workbench page', () => { }); cy.get('@editWorkbench.all').then((interceptions) => { - expect(interceptions).to.have.length(2); // 1 dry run request and 1 actaul request + expect(interceptions).to.have.length(2); // 1 dry run request and 1 actual request }); }); + it('Handle deleted notebook sizes in workbenches table', () => { + initIntercepts({ + notebookSizes: [ + { + name: 'XSmall', + resources: { + limits: { + cpu: '0.5', + memory: '500Mi', + }, + requests: { + cpu: '0.1', + memory: '100Mi', + }, + }, + }, + ], + }); + workbenchPage.visit('test-project'); + workbenchPage.findCreateButton().click(); + verifyRelativeURL('/projects/test-project/spawner'); + // Custom container size dropdown option should not be present for create workbench + createSpawnerPage.findContainerSizeInput('Keep custom size').should('not.exist'); + cy.go('back'); + const notebookRow = workbenchPage.getNotebookRow('Test Notebook'); + notebookRow.shouldHaveNotebookImageName('Test Image'); + notebookRow.shouldHaveContainerSize('Custom'); + notebookRow.findKebabAction('Edit workbench').click(); + editSpawnerPage.shouldHaveContainerSizeInput('Keep custom size'); + }); + it('Validate that updating invalid workbench will navigate to the new page with an error message', () => { initIntercepts({}); notFoundSpawnerPage.visit('updated-notebook'); diff --git a/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx b/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx index a75a88a6d4..09a488652c 100644 --- a/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx +++ b/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx @@ -52,6 +52,7 @@ const NotebookList: React.FC = () => {