Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp empty state in Model Serving Global #1835

Merged
merged 1 commit into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,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 @@ -252,9 +250,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,14 @@
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,
EmptyStateSecondaryActions,
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,17 +21,20 @@ 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>
<EmptyStateSecondaryActions>
<Button variant="link" onClick={() => navigate('/projects')}>
Select a project
DaoDaoNoCode marked this conversation as resolved.
Show resolved Hide resolved
</Button>
</EmptyStateSecondaryActions>
</EmptyState>
);
}
Expand All @@ -32,7 +43,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/*"]
"~/*": ["./*"]
lucferbux marked this conversation as resolved.
Show resolved Hide resolved
},
"importHelpers": true,
"skipLibCheck": true
Expand Down
Loading