Skip to content

Commit

Permalink
Merge pull request #2109 from DaoDaoNoCode/upstream-issue-1994
Browse files Browse the repository at this point in the history
Add empty state when no serving platform is enabled
  • Loading branch information
openshift-merge-bot[bot] authored Nov 10, 2023
2 parents 4ccc506 + 82408ab commit 11e2189
Show file tree
Hide file tree
Showing 20 changed files with 367 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { rest } from 'msw';
import { within } from '@storybook/testing-library';
import { mockClusterSettings } from '~/__mocks__/mockClusterSettings';
import ClusterSettings from '~/pages/clusterSettings/ClusterSettings';
import { AreaContext } from '~/concepts/areas/AreaContext';
import { mockDscStatus } from '~/__mocks__/mockDscStatus';
import { StackComponent } from '~/concepts/areas';

export default {
component: ClusterSettings,
Expand All @@ -18,7 +21,17 @@ export default {
},
} as Meta<typeof ClusterSettings>;

const Template: StoryFn<typeof ClusterSettings> = (args) => <ClusterSettings {...args} />;
const Template: StoryFn<typeof ClusterSettings> = (args) => (
<AreaContext.Provider
value={{
dscStatus: mockDscStatus({
installedComponents: { [StackComponent.K_SERVE]: true, [StackComponent.MODEL_MESH]: true },
}),
}}
>
<ClusterSettings {...args} />
</AreaContext.Provider>
);

export const Default: StoryObj = {
render: Template,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {
import { mockSecretK8sResource } from '~/__mocks__/mockSecretK8sResource';
import ModelServingContextProvider from '~/pages/modelServing/ModelServingContext';
import ModelServingGlobal from '~/pages/modelServing/screens/global/ModelServingGlobal';
import { AreaContext } from '~/concepts/areas/AreaContext';
import { mockDscStatus } from '~/__mocks__/mockDscStatus';
import { StackComponent } from '~/concepts/areas';

export default {
component: ModelServingGlobal,
Expand Down Expand Up @@ -56,11 +59,19 @@ export default {
} as Meta<typeof ModelServingGlobal>;

const Template: StoryFn<typeof ModelServingGlobal> = (args) => (
<Routes>
<Route path="/" element={<ModelServingContextProvider />}>
<Route index element={<ModelServingGlobal {...args} />} />
</Route>
</Routes>
<AreaContext.Provider
value={{
dscStatus: mockDscStatus({
installedComponents: { [StackComponent.K_SERVE]: true, [StackComponent.MODEL_MESH]: true },
}),
}}
>
<Routes>
<Route path="/" element={<ModelServingContextProvider />}>
<Route index element={<ModelServingGlobal {...args} />} />
</Route>
</Routes>
</AreaContext.Provider>
);

export const EmptyStateNoServingRuntime: StoryObj = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ test('Deploy KServe model', async ({ page }) => {
await expect(await page.getByRole('button', { name: 'Deploy', exact: true })).toBeEnabled();
});

test('No model serving platform available', async ({ page }) => {
await page.goto(
navigateToStory(
'pages-modelserving-servingruntimelist',
'neither-platform-enabled-and-project-not-labelled',
),
);

expect(page.getByText('No model serving platform selected')).toBeTruthy();
});

test('ModelMesh ServingRuntime list', async ({ page }) => {
await page.goto(
navigateToStory('pages-modelserving-servingruntimelist', 'model-mesh-list-available-models'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ import { AppContext } from '~/app/AppContext';
import { useApplicationSettings } from '~/app/useApplicationSettings';
import { ServingRuntimeKind } from '~/k8sTypes';
import { ServingRuntimePlatform } from '~/types';
import { AreaContext } from '~/concepts/areas/AreaContext';
import { mockDscStatus } from '~/__mocks__/mockDscStatus';
import { StackComponent } from '~/concepts/areas';

type HandlersProps = {
disableKServeConfig?: boolean;
Expand Down Expand Up @@ -181,11 +184,22 @@ const Template: StoryFn<typeof ModelServingPlatform> = (args) => {
const { dashboardConfig, loaded } = useApplicationSettings();
return loaded && dashboardConfig ? (
<AppContext.Provider value={{ buildStatuses: [], dashboardConfig }}>
<ProjectsRoutes>
<Route path="/" element={<ProjectDetailsContextProvider />}>
<Route index element={<ModelServingPlatform {...args} />} />
</Route>
</ProjectsRoutes>
<AreaContext.Provider
value={{
dscStatus: mockDscStatus({
installedComponents: {
[StackComponent.K_SERVE]: true,
[StackComponent.MODEL_MESH]: true,
},
}),
}}
>
<ProjectsRoutes>
<Route path="/" element={<ProjectDetailsContextProvider />}>
<Route index element={<ModelServingPlatform {...args} />} />
</Route>
</ProjectsRoutes>
</AreaContext.Provider>
</AppContext.Provider>
) : (
<Spinner />
Expand Down Expand Up @@ -220,6 +234,20 @@ export const OnlyEnabledModelMeshAndProjectNotLabelled: StoryObj = {
},
};

export const NeitherPlatformEnabledAndProjectNotLabelled: StoryObj = {
render: Template,

parameters: {
msw: {
handlers: getHandlers({
disableModelMeshConfig: false,
disableKServeConfig: false,
servingRuntimes: [],
}),
},
},
};

export const ModelMeshListAvailableModels: StoryObj = {
render: Template,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ test('Empty project', async ({ page }) => {
await page.goto(navigateToStory('pages-projects-projectdetails', 'empty-details-page'));

// wait for page to load
await page.waitForSelector('text=No model servers');
await page.waitForSelector('text=Models and model servers');

// the dividers number should always 1 less than the section number
const sections = await page.locator('[data-id="details-page-section"]').all();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import { StoryFn, Meta, StoryObj } from '@storybook/react';
import { DefaultBodyType, MockedRequest, rest, RestHandler } from 'msw';
import { within } from '@storybook/testing-library';
import { Route } from 'react-router-dom';
import {
mockRouteK8sResource,
Expand All @@ -25,6 +24,9 @@ import { mockStatus } from '~/__mocks__/mockStatus';
import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource';
import { mockDashboardConfig } from '~/__mocks__/mockDashboardConfig';
import ProjectDetails from '~/pages/projects/screens/detail/ProjectDetails';
import { AreaContext } from '~/concepts/areas/AreaContext';
import { mockDscStatus } from '~/__mocks__/mockDscStatus';
import { StackComponent } from '~/concepts/areas';

const handlers = (isEmpty: boolean): RestHandler<MockedRequest<DefaultBodyType>>[] => [
rest.get('/api/status', (req, res, ctx) => res(ctx.json(mockStatus()))),
Expand Down Expand Up @@ -144,21 +146,28 @@ export default {
const Template: StoryFn<typeof ProjectDetails> = (args) => {
useDetectUser();
return (
<ProjectsRoutes>
<Route path="/" element={<ProjectDetailsContextProvider />}>
<Route index element={<ProjectDetails {...args} />} />
</Route>
</ProjectsRoutes>
<AreaContext.Provider
value={{
dscStatus: mockDscStatus({
installedComponents: {
[StackComponent.WORKBENCHES]: true,
[StackComponent.K_SERVE]: true,
[StackComponent.MODEL_MESH]: true,
},
}),
}}
>
<ProjectsRoutes>
<Route path="/" element={<ProjectDetailsContextProvider />}>
<Route index element={<ProjectDetails {...args} />} />
</Route>
</ProjectsRoutes>
</AreaContext.Provider>
);
};

export const Default: StoryObj = {
render: Template,
play: async ({ canvasElement }) => {
// load page and wait until settled
const canvas = within(canvasElement);
await canvas.findByText('Test Notebook', undefined, { timeout: 5000 });
},
};

export const EmptyDetailsPage: StoryObj = {
Expand All @@ -169,10 +178,4 @@ export const EmptyDetailsPage: StoryObj = {
handlers: handlers(true),
},
},

play: async ({ canvasElement }) => {
// load page and wait until settled
const canvas = within(canvasElement);
await canvas.findByText('No model servers', undefined, { timeout: 5000 });
},
};
61 changes: 20 additions & 41 deletions frontend/src/concepts/areas/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,71 +128,50 @@ describe('isAreaAvailable', () => {
});

/**
* These tests rely on Model Serving being in a specific configuration, we may need to replace
* These tests rely on Custom Serving Runtime being in a specific configuration, we may need to replace
* these tests if these become obsolete.
*/
describe('reliantAreas', () => {
it('should enable area if at least one reliant area is enabled', () => {
// Make sure this test is valid
expect(SupportedAreasStateMap[SupportedArea.MODEL_SERVING].reliantAreas).toEqual([
SupportedArea.K_SERVE,
SupportedArea.MODEL_MESH,
expect(SupportedAreasStateMap[SupportedArea.CUSTOM_RUNTIMES].reliantAreas).toEqual([
SupportedArea.MODEL_SERVING,
]);

// Test both reliant areas
const isAvailableReliantModelMesh = isAreaAvailable(
SupportedArea.MODEL_SERVING,
const isAvailableReliantCustomRuntimes = isAreaAvailable(
SupportedArea.CUSTOM_RUNTIMES,
mockDashboardConfig({ disableModelServing: false }).spec,
mockDscStatus({ installedComponents: { [StackComponent.MODEL_MESH]: true } }),
mockDscStatus({}),
);

expect(isAvailableReliantModelMesh.status).toBe(true);
expect(isAvailableReliantModelMesh.featureFlags).toEqual({ ['disableModelServing']: 'on' });
expect(isAvailableReliantModelMesh.reliantAreas).toEqual({
[SupportedArea.K_SERVE]: false,
[SupportedArea.MODEL_MESH]: true,
expect(isAvailableReliantCustomRuntimes.status).toBe(true);
expect(isAvailableReliantCustomRuntimes.featureFlags).toEqual({
['disableCustomServingRuntimes']: 'on',
});
expect(isAvailableReliantModelMesh.requiredComponents).toBe(null);

const isAvailableReliantKServe = isAreaAvailable(
SupportedArea.MODEL_SERVING,
mockDashboardConfig({ disableModelServing: false }).spec,
mockDscStatus({ installedComponents: { [StackComponent.K_SERVE]: true } }),
);

expect(isAvailableReliantKServe.status).toBe(true);
expect(isAvailableReliantKServe.featureFlags).toEqual({ ['disableModelServing']: 'on' });
expect(isAvailableReliantKServe.reliantAreas).toEqual({
[SupportedArea.K_SERVE]: true,
[SupportedArea.MODEL_MESH]: false,
expect(isAvailableReliantCustomRuntimes.reliantAreas).toEqual({
[SupportedArea.MODEL_SERVING]: true,
});
expect(isAvailableReliantKServe.requiredComponents).toBe(null);
expect(isAvailableReliantCustomRuntimes.requiredComponents).toBe(null);
});

it('should disable area if reliant areas are all disabled', () => {
// Make sure this test is valid
expect(SupportedAreasStateMap[SupportedArea.MODEL_SERVING].reliantAreas).toEqual([
SupportedArea.K_SERVE,
SupportedArea.MODEL_MESH,
expect(SupportedAreasStateMap[SupportedArea.CUSTOM_RUNTIMES].reliantAreas).toEqual([
SupportedArea.MODEL_SERVING,
]);

// Test both areas disabled
// Test areas disabled
const isAvailable = isAreaAvailable(
SupportedArea.MODEL_SERVING,
mockDashboardConfig({ disableModelServing: false }).spec,
mockDscStatus({
installedComponents: {
[StackComponent.K_SERVE]: false,
[StackComponent.MODEL_MESH]: false,
},
}),
SupportedArea.CUSTOM_RUNTIMES,
mockDashboardConfig({ disableModelServing: true }).spec,
mockDscStatus({}),
);

expect(isAvailable.status).not.toBe(true);
expect(isAvailable.featureFlags).toEqual({ ['disableModelServing']: 'on' });
expect(isAvailable.featureFlags).toEqual({ ['disableCustomServingRuntimes']: 'on' });
expect(isAvailable.reliantAreas).toEqual({
[SupportedArea.K_SERVE]: false,
[SupportedArea.MODEL_MESH]: false,
[SupportedArea.MODEL_SERVING]: false,
});
expect(isAvailable.requiredComponents).toBe(null);
});
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/concepts/areas/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,15 @@ export const SupportedAreasStateMap: SupportedAreasState = {
reliantAreas: [SupportedArea.DS_PROJECTS_VIEW],
},
[SupportedArea.K_SERVE]: {
//featureFlags: ['disableKServe'], // TODO: validate KServe feature flag
featureFlags: ['disableKServe'],
requiredComponents: [StackComponent.K_SERVE],
},
[SupportedArea.MODEL_MESH]: {
//featureFlags: ['disableModelMesh'], // TODO: validate ModelMesh feature flag
featureFlags: ['disableModelMesh'],
requiredComponents: [StackComponent.MODEL_MESH],
},
[SupportedArea.MODEL_SERVING]: {
featureFlags: ['disableModelServing'],
reliantAreas: [SupportedArea.K_SERVE, SupportedArea.MODEL_MESH],
},
[SupportedArea.USER_MANAGEMENT]: {
featureFlags: ['disableUserManagement'],
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/concepts/areas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
determine the state we are in.
*/
export { default as AreaComponent, conditionalArea } from './AreaComponent';
export { SupportedArea } from './types';
export { SupportedArea, StackComponent } from './types';
export { default as useIsAreaAvailable } from './useIsAreaAvailable';
export { isAreaAvailable } from './utils';
18 changes: 11 additions & 7 deletions frontend/src/pages/clusterSettings/ClusterSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import CullerSettings from '~/pages/clusterSettings/CullerSettings';
import TelemetrySettings from '~/pages/clusterSettings/TelemetrySettings';
import TolerationSettings from '~/pages/clusterSettings/TolerationSettings';
import ModelServingPlatformSettings from '~/pages/clusterSettings/ModelServingPlatformSettings';
import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas';
import {
DEFAULT_CONFIG,
DEFAULT_PVC_SIZE,
Expand All @@ -34,6 +35,7 @@ const ClusterSettings: React.FC = () => {
const [userTrackingEnabled, setUserTrackingEnabled] = React.useState(false);
const [cullerTimeout, setCullerTimeout] = React.useState(DEFAULT_CULLER_TIMEOUT);
const { dashboardConfig } = useAppContext();
const modelServingEnabled = useIsAreaAvailable(SupportedArea.MODEL_SERVING);
const isJupyterEnabled = useCheckJupyterEnabled();
const [notebookTolerationSettings, setNotebookTolerationSettings] =
React.useState<NotebookTolerationFormSettings>({
Expand Down Expand Up @@ -140,13 +142,15 @@ const ClusterSettings: React.FC = () => {
provideChildrenPadding
>
<Stack hasGutter>
<StackItem>
<ModelServingPlatformSettings
initialValue={clusterSettings.modelServingPlatformEnabled}
enabledPlatforms={modelServingEnabledPlatforms}
setEnabledPlatforms={setModelServingEnabledPlatforms}
/>
</StackItem>
{modelServingEnabled && (
<StackItem>
<ModelServingPlatformSettings
initialValue={clusterSettings.modelServingPlatformEnabled}
enabledPlatforms={modelServingEnabledPlatforms}
setEnabledPlatforms={setModelServingEnabledPlatforms}
/>
</StackItem>
)}
<StackItem>
<PVCSizeSettings
initialValue={clusterSettings.pvcSize}
Expand Down
Loading

0 comments on commit 11e2189

Please sign in to comment.