Skip to content

Commit

Permalink
Revamp empty state in Model Serving Global
Browse files Browse the repository at this point in the history
  • Loading branch information
lucferbux committed Sep 20, 2023
1 parent caf5caf commit 2b3949d
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 61 deletions.
8 changes: 2 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,7 @@ export const EditModel = {
import { test, expect } from '@playwright/test';

test('Create project', async ({ page }) => {
await page.goto(
'./iframe.html?id=tests-stories-pages-projects-projectview--create-project&viewMode=story',
);
await page.goto(navigateToStory('projects-projectview', 'create-project'));

// wait for page to load
await page.waitForSelector('text=Create data science project');
Expand All @@ -217,9 +215,7 @@ test('Create project', async ({ page }) => {
To run storybook UI: `cd ./frontend && npm run storybook`

```ts
await page.goto(
'./iframe.html?id=tests-stories-pages-projects-projectview--create-project&viewMode=story',
);
await page.goto(navigateToStory('projects-projectview', 'create-project'));
```

6. Wait for the page to load and the story to settle before performing any assertions or actions. Use `page.waitForSelector()` to wait for a specific element to appear as an indication of the story being loaded.
Expand Down
17 changes: 5 additions & 12 deletions frontend/src/__tests__/integration/hooks/useFetchState.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { test, expect } from '@playwright/test';
import { navigateToStory } from '~/__tests__/integration/utils';

test('Success', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-hooks-usefetchstate--success&viewMode=story',
);
await page.goto(navigateToStory('hooks-usefetchstate', 'success'));

// wait 2 seconds to settle
await new Promise((resolve) => setTimeout(resolve, 2000));
Expand All @@ -16,9 +15,7 @@ test('Success', async ({ page }) => {
});

test('Failure', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-hooks-usefetchstate--failure&viewMode=story',
);
await page.goto(navigateToStory('hooks-usefetchstate', 'failure'));

// wait 2 seconds to settle
await new Promise((resolve) => setTimeout(resolve, 2000));
Expand All @@ -34,9 +31,7 @@ test('Failure', async ({ page }) => {
});

test('Stable', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-hooks-usefetchstate--stable&viewMode=story',
);
await page.goto(navigateToStory('hooks-usefetchstate', 'stable'));

// wait 2 seconds to settle
await new Promise((resolve) => setTimeout(resolve, 2000));
Expand Down Expand Up @@ -76,9 +71,7 @@ test('Stable', async ({ page }) => {
});

test('Refresh rate', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-hooks-usefetchstate--refresh-rate&viewMode=story',
);
await page.goto(navigateToStory('hooks-usefetchstate', 'refresh-rate'));

// wait 2 seconds to settle
await new Promise((resolve) => setTimeout(resolve, 2000));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { test, expect } from '@playwright/test';
import { navigateToStory } from '~/__tests__/integration/utils';

test('Cluster settings', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-pages-clustersettings-clustersettings--default&viewMode=story',
);
await page.goto(navigateToStory('pages-clustersettings-clustersettings', 'default'));
// wait for page to load
await page.waitForSelector('text=Save changes');
const submitButton = page.locator('[data-id="submit-cluster-settings"]');
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
navigateToStory('pages-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(
'./iframe.html?args=&id=tests-integration-pages-modelserving-modelservingglobal--delete-model&viewMode=story',
navigateToStory('pages-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('pages-modelserving-modelservingglobal', 'delete-model'));

// wait for page to load
await page.waitForSelector('text=Delete deployed model?');

Expand All @@ -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('pages-modelserving-modelservingglobal', 'edit-model'));

// wait for page to load
await page.waitForSelector('text=Deploy model');
Expand Down Expand Up @@ -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('pages-modelserving-modelservingglobal', 'deploy-model'));

// wait for page to load
await page.waitForSelector('text=Deploy model');
Expand Down Expand Up @@ -94,9 +116,7 @@ test('Create model', async ({ page }) => {
});

test('Create model error', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-pages-modelserving-modelservingglobal--deploy-model&viewMode=story',
);
await page.goto(navigateToStory('pages-modelserving-modelservingglobal', 'deploy-model'));

// wait for page to load
await page.waitForSelector('text=Deploy model');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,51 @@ const Template: StoryFn<typeof ModelServingGlobal> = (args) => (
</Routes>
);

export const EmptyStateNoServingRuntime: StoryObj = {
render: Template,

parameters: {
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: {
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,

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { test, expect } from '@playwright/test';
import { navigateToStory } from '~/__tests__/integration/utils';

test('Deploy model', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-pages-modelserving-servingruntimelist--deploy-model&viewMode=story',
);
await page.goto(navigateToStory('pages-modelserving-servingruntimelist', 'deploy-model'));

// wait for page to load
await page.waitForSelector('text=Deploy model');
Expand Down Expand Up @@ -38,7 +37,7 @@ test('Deploy model', async ({ page }) => {

test('Legacy Serving Runtime', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-pages-modelserving-servingruntimelist--list-available-models&viewMode=story',
navigateToStory('pages-modelserving-servingruntimelist', 'list-available-models'),
);

// wait for page to load
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { test, expect } from '@playwright/test';
import { navigateToStory } from '~/__tests__/integration/utils';

test('Empty project', async ({ page }) => {
await page.goto(
'./iframe.html?args=&id=tests-integration-pages-projects-projectdetails--empty-details-page&viewMode=story',
);
await page.goto(navigateToStory('pages-projects-projectdetails', 'empty-details-page'));

// wait for page to load
await page.waitForSelector('text=No model servers');
Expand All @@ -16,9 +15,7 @@ test('Empty project', async ({ page }) => {
});

test('Non-empty project', async ({ page }) => {
await page.goto(
'./iframe.html?id=tests-integration-pages-projects-projectdetails--default&viewMode=story',
);
await page.goto(navigateToStory('pages-projects-projectdetails', 'default'));

// wait for page to load
await page.waitForSelector('text=Test Notebook');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { test, expect } from '@playwright/test';
import { navigateToStory } from '~/__tests__/integration/utils';

test('Create project', async ({ page }) => {
await page.goto(
'./iframe.html?id=tests-integration-pages-projects-projectview--create-project&viewMode=story',
);
await page.goto(navigateToStory('pages-projects-projectview', 'create-project'));

// wait for page to load
await page.waitForSelector('text=Create data science project');
Expand Down Expand Up @@ -52,9 +51,7 @@ test('Create project', async ({ page }) => {
});

test('Edit project', async ({ page }) => {
await page.goto(
'./iframe.html?id=tests-integration-pages-projects-projectview--edit-project&viewMode=story',
);
await page.goto(navigateToStory('pages-projects-projectview', 'edit-project'));

// wait for page to load
await page.waitForSelector('text=Edit data science project');
Expand All @@ -71,9 +68,7 @@ test('Edit project', async ({ page }) => {
});

test('Delete project', async ({ page }) => {
await page.goto(
'./iframe.html?id=tests-integration-pages-projects-projectview--delete-project&viewMode=story',
);
await page.goto(navigateToStory('pages-projects-projectview', 'delete-project'));

// wait for page to load
await page.waitForSelector('text=Delete project?');
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/__tests__/integration/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const navigateToStory = (folder: string, storyId: string) =>
`./iframe.html?args=&id=tests-integration-${folder}--${storyId}&viewMode=story`;
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -13,16 +20,17 @@ const EmptyModelServing: React.FC = () => {

if (servingRuntimes.length === 0) {
return (
<EmptyState>
<EmptyStateIcon icon={PlusCircleIcon} />
<EmptyState variant={EmptyStateVariant.small}>
<EmptyStateIcon icon={WrenchIcon} />
<Title headingLevel="h2" size="lg">
No model servers
No deployed models yet
</Title>
<EmptyStateBody>
Before deploying a model, you must first configure a model server.
To get started, deploy a model from the <strong>Models and model servers</strong> section
of a project.
</EmptyStateBody>
<Button variant="primary" onClick={() => navigate('/projects')}>
Create server
<Button variant="link" onClick={() => navigate('/projects')}>
Select a project
</Button>
</EmptyState>
);
Expand All @@ -32,7 +40,7 @@ const EmptyModelServing: React.FC = () => {
<EmptyState>
<EmptyStateIcon icon={PlusCircleIcon} />
<Title headingLevel="h2" size="lg">
No deployed models.
No deployed models
</Title>
<EmptyStateBody>To get started, use existing model servers to serve a model.</EmptyStateBody>
<ServeModelButton />
Expand Down
3 changes: 2 additions & 1 deletion frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"baseUrl": "./src",
"paths": {
"~/*": ["./src/*"]
"~/*": ["./*"]
},
"importHelpers": true,
"skipLibCheck": true
Expand Down

0 comments on commit 2b3949d

Please sign in to comment.