From dc1f4ca3ef5da22e590a772242af2d3903c47d88 Mon Sep 17 00:00:00 2001 From: Lucas Fernandez Date: Mon, 18 Sep 2023 15:59:10 +0200 Subject: [PATCH] Revamp empty state in Model Serving Global --- .../modelServing/ModelServingGlobal.spec.ts | 38 ++++++++++--- .../ModelServingGlobal.stories.tsx | 53 +++++++++++++++++++ frontend/src/__tests__/integration/utils.ts | 2 + .../screens/global/EmptyModelServing.tsx | 24 ++++++--- 4 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 frontend/src/__tests__/integration/utils.ts diff --git a/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.spec.ts b/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.spec.ts index 9b21197786..4b39fa6d2e 100644 --- a/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.spec.ts +++ b/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.spec.ts @@ -1,10 +1,36 @@ import { test, expect } from '@playwright/test'; +import { navigateToStory } from '~/__tests__/integration/utils'; -test('Delete model', async ({ page }) => { +test('Empty State No Serving Runtime', async ({ page }) => { await page.goto( - './iframe.html?args=&id=tests-integration-pages-modelserving-modelservingglobal--delete-model&viewMode=story', + navigateToStory('modelserving-modelservingglobal', 'empty-state-no-serving-runtime'), ); + // wait for page to load + await page.waitForSelector('text=No deployed models yet'); + + // Test that the button is enabled + await expect(page.getByRole('button', { name: 'Go to the Projects page' })).toBeTruthy(); +}); + +test('Empty State No Inference Service', async ({ page }) => { + await page.goto( + navigateToStory('modelserving-modelservingglobal', 'empty-state-no-inference-service'), + ); + + // wait for page to load + await page.waitForSelector('text=No deployed models'); + + // Test that the button is enabled + await page.getByRole('button', { name: 'Deploy model' }).click(); + + // test that you can not submit on empty + await expect(await page.getByRole('button', { name: 'Deploy' })).toBeDisabled(); +}); + +test('Delete model', async ({ page }) => { + await page.goto(navigateToStory('modelserving-modelservingglobal', 'delete-model')); + // wait for page to load await page.waitForSelector('text=Delete deployed model?'); @@ -19,9 +45,7 @@ test('Delete model', async ({ page }) => { }); test('Edit model', async ({ page }) => { - await page.goto( - './iframe.html?args=&id=tests-integration-pages-modelserving-modelservingglobal--edit-model&viewMode=story', - ); + await page.goto(navigateToStory('modelserving-modelservingglobal', 'edit-model')); // wait for page to load await page.waitForSelector('text=Deploy model'); @@ -53,9 +77,7 @@ test('Edit model', async ({ page }) => { }); test('Create model', async ({ page }) => { - await page.goto( - './iframe.html?args=&id=tests-integration-pages-modelserving-modelservingglobal--deploy-model&viewMode=story', - ); + await page.goto(navigateToStory('modelserving-modelservingglobal', 'deploy-model')); // wait for page to load await page.waitForSelector('text=Deploy model'); diff --git a/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx b/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx index 5861976db9..14689a65d0 100644 --- a/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx +++ b/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx @@ -51,6 +51,59 @@ const Template: StoryFn = (args) => ( ); +export const EmptyStateNoServingRuntime: StoryObj = { + render: Template, + + parameters: { + a11y: { + // need to select modal as root + element: '.pf-c-backdrop', + }, + msw: { + handlers: [ + rest.get( + 'api/k8s/apis/serving.kserve.io/v1alpha1/namespaces/test-project/servingruntimes', + (req, res, ctx) => res(ctx.json(mockK8sResourceList([]))), + ), + rest.get( + 'api/k8s/apis/serving.kserve.io/v1beta1/namespaces/test-project/inferenceservices', + (req, res, ctx) => res(ctx.json(mockK8sResourceList([]))), + ), + rest.get('/api/k8s/apis/project.openshift.io/v1/projects', (req, res, ctx) => + res(ctx.json(mockK8sResourceList([mockProjectK8sResource({})]))), + ), + ], + }, + }, +}; + +export const EmptyStateNoInferenceServices: StoryObj = { + render: Template, + + parameters: { + a11y: { + // need to select modal as root + element: '.pf-c-backdrop', + }, + msw: { + handlers: [ + rest.get( + 'api/k8s/apis/serving.kserve.io/v1alpha1/namespaces/test-project/servingruntimes', + (req, res, ctx) => + res(ctx.json(mockK8sResourceList([mockServingRuntimeK8sResource({})]))), + ), + rest.get( + 'api/k8s/apis/serving.kserve.io/v1beta1/namespaces/test-project/inferenceservices', + (req, res, ctx) => res(ctx.json(mockK8sResourceList([]))), + ), + rest.get('/api/k8s/apis/project.openshift.io/v1/projects', (req, res, ctx) => + res(ctx.json(mockK8sResourceList([mockProjectK8sResource({})]))), + ), + ], + }, + }, +}; + export const EditModel: StoryObj = { render: Template, diff --git a/frontend/src/__tests__/integration/utils.ts b/frontend/src/__tests__/integration/utils.ts new file mode 100644 index 0000000000..d7cacc3bef --- /dev/null +++ b/frontend/src/__tests__/integration/utils.ts @@ -0,0 +1,2 @@ +export const navigateToStory = (folder: string, storyId: string) => + `./iframe.html?args=&id=tests-integration-pages-${folder}--${storyId}&viewMode=story`; diff --git a/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx b/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx index 4bd7471bd9..5e68d7434b 100644 --- a/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx +++ b/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx @@ -1,6 +1,13 @@ import * as React from 'react'; -import { Button, EmptyState, EmptyStateBody, EmptyStateIcon, Title } from '@patternfly/react-core'; -import { PlusCircleIcon } from '@patternfly/react-icons'; +import { + Button, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + EmptyStateVariant, + Title, +} from '@patternfly/react-core'; +import { PlusCircleIcon, WrenchIcon } from '@patternfly/react-icons'; import { useNavigate } from 'react-router-dom'; import { ModelServingContext } from '~/pages/modelServing/ModelServingContext'; import ServeModelButton from './ServeModelButton'; @@ -13,16 +20,17 @@ const EmptyModelServing: React.FC = () => { if (servingRuntimes.length === 0) { return ( - - + + - No model servers + No deployed models yet - Before deploying a model, you must first configure a model server. + To deploy a model, go to the Projects page and select the project from which the model + will be deployed. Then jump down to the Models and Model Servers section. - );