Skip to content

Commit 4e01d1f

Browse files
authored
Link refactor deploy buttons to existing deploy modal (opendatahub-io#4542)
* Link refactor deploy buttons to existing deploy modal * Adjustments from feedback * Add toolbar for refactored global models page * Small bug fixes * Refine some areas * Remove references to refresh * Clean up code and fix deploy button in global no models
1 parent 17875a8 commit 4e01d1f

File tree

5 files changed

+187
-15
lines changed

5 files changed

+187
-15
lines changed
Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,141 @@
11
import React from 'react';
22
import { Button, Tooltip } from '@patternfly/react-core';
3-
import { ModelServingPlatform } from '../../concepts/useProjectServingPlatform';
3+
import { ServingRuntimePlatform } from '@odh-dashboard/internal/types';
4+
import {
5+
getSortedTemplates,
6+
getTemplateEnabled,
7+
getTemplateEnabledForPlatform,
8+
} from '@odh-dashboard/internal/pages/modelServing/customServingRuntimes/utils';
9+
import ManageServingRuntimeModal from '@odh-dashboard/internal/pages/modelServing/screens/projects/ServingRuntimeModal/ManageServingRuntimeModal';
10+
import ManageKServeModal from '@odh-dashboard/internal/pages/modelServing/screens/projects/kServeModal/ManageKServeModal';
11+
import ManageNIMServingModal from '@odh-dashboard/internal/pages/modelServing/screens/projects/NIMServiceModal/ManageNIMServingModal';
12+
import { byName, ProjectsContext } from '@odh-dashboard/internal/concepts/projects/ProjectsContext';
13+
import { useParams } from 'react-router-dom';
14+
import { useTemplates } from '@odh-dashboard/internal/api/k8s/templates';
15+
import { useDashboardNamespace } from '@odh-dashboard/internal/redux/selectors/index';
16+
import useTemplateOrder from '@odh-dashboard/internal/pages/modelServing/customServingRuntimes/useTemplateOrder';
17+
import useTemplateDisablement from '@odh-dashboard/internal/pages/modelServing/customServingRuntimes/useTemplateDisablement';
18+
import useConnections from '@odh-dashboard/internal/pages/projects/screens/detail/connections/useConnections';
19+
import { ProjectKind } from '@odh-dashboard/internal/k8sTypes';
20+
import { isProjectNIMSupported } from '@odh-dashboard/internal/pages/modelServing/screens/projects/nimUtils';
21+
import { ModelDeploymentsContext } from '../../concepts/ModelDeploymentsContext';
22+
import {
23+
useProjectServingPlatform,
24+
ModelServingPlatform,
25+
} from '../../concepts/useProjectServingPlatform';
26+
import { useAvailableClusterPlatforms } from '../../concepts/useAvailableClusterPlatforms';
27+
28+
const DeployButtonModal: React.FC<{
29+
platform: ServingRuntimePlatform;
30+
currentProject: ProjectKind;
31+
namespace?: string;
32+
onClose: (submit: boolean) => void;
33+
}> = ({ platform, currentProject, namespace, onClose }) => {
34+
const { dashboardNamespace } = useDashboardNamespace();
35+
const [servingRuntimeTemplates] = useTemplates(dashboardNamespace);
36+
const servingRuntimeTemplateOrder = useTemplateOrder(dashboardNamespace, undefined);
37+
const servingRuntimeTemplateDisablement = useTemplateDisablement(dashboardNamespace, undefined);
38+
const connections = useConnections(namespace || '');
39+
const templatesSorted = getSortedTemplates(
40+
servingRuntimeTemplates,
41+
servingRuntimeTemplateOrder.data,
42+
);
43+
const templatesEnabled = templatesSorted.filter((template) =>
44+
getTemplateEnabled(template, servingRuntimeTemplateDisablement.data),
45+
);
46+
47+
if (platform === ServingRuntimePlatform.MULTI) {
48+
return (
49+
<ManageServingRuntimeModal
50+
currentProject={currentProject}
51+
servingRuntimeTemplates={templatesEnabled.filter((t) =>
52+
getTemplateEnabledForPlatform(t, ServingRuntimePlatform.MULTI),
53+
)}
54+
onClose={onClose}
55+
/>
56+
);
57+
}
58+
59+
const isNIMSupported = isProjectNIMSupported(currentProject);
60+
if (isNIMSupported) {
61+
return <ManageNIMServingModal projectContext={{ currentProject }} onClose={onClose} />;
62+
}
63+
return (
64+
<ManageKServeModal
65+
projectContext={{ currentProject, connections: connections.data }}
66+
servingRuntimeTemplates={templatesEnabled.filter((t) =>
67+
getTemplateEnabledForPlatform(t, ServingRuntimePlatform.SINGLE),
68+
)}
69+
onClose={onClose}
70+
/>
71+
);
72+
};
473

574
export const DeployButton: React.FC<{
675
platform?: ModelServingPlatform;
776
variant?: 'primary' | 'secondary';
877
isDisabled?: boolean;
978
}> = ({ platform, variant = 'primary', isDisabled }) => {
79+
const [modalShown, setModalShown] = React.useState<boolean>(false);
80+
const [platformSelected, setPlatformSelected] = React.useState<ServingRuntimePlatform>();
81+
const { namespace: modelNamespace } = useParams<{ namespace: string }>();
82+
const { projects } = React.useContext(ProjectsContext);
83+
const { clusterPlatforms } = useAvailableClusterPlatforms();
84+
const { projects: modelProjects } = React.useContext(ModelDeploymentsContext);
85+
const match = modelNamespace ? projects.find(byName(modelNamespace)) : undefined;
86+
const currentProject = modelProjects?.find(byName(modelNamespace));
87+
const { activePlatform, projectPlatform } = useProjectServingPlatform(match, clusterPlatforms);
88+
89+
const getServingRuntimePlatform = (platformId: string): ServingRuntimePlatform =>
90+
platformId === 'modelmesh' ? ServingRuntimePlatform.MULTI : ServingRuntimePlatform.SINGLE;
91+
92+
const onSubmit = () => {
93+
setModalShown(false);
94+
setPlatformSelected(undefined);
95+
};
96+
97+
const handleDeployClick = () => {
98+
if (platform) {
99+
setPlatformSelected(getServingRuntimePlatform(platform.properties.id));
100+
} else {
101+
const currentPlatform = activePlatform || projectPlatform;
102+
if (currentProject && currentPlatform) {
103+
setPlatformSelected(getServingRuntimePlatform(currentPlatform.properties.id));
104+
}
105+
}
106+
setModalShown(true);
107+
};
108+
10109
const deployButton = (
11110
<Button
12111
variant={variant}
13112
data-testid="deploy-button"
14-
// onClick={() => {
15-
// do something
16-
// }}
17-
isAriaDisabled={isDisabled}
113+
onClick={handleDeployClick}
114+
isAriaDisabled={!currentProject}
18115
>
19116
Deploy model
20117
</Button>
21118
);
22-
if (!platform || isDisabled) {
119+
120+
if (!currentProject || isDisabled) {
23121
return (
24122
<Tooltip data-testid="deploy-model-tooltip" content="To deploy a model, select a project.">
25123
{deployButton}
26124
</Tooltip>
27125
);
28126
}
29-
return <>{deployButton}</>;
127+
128+
return (
129+
<>
130+
{deployButton}
131+
{modalShown && platformSelected ? (
132+
<DeployButtonModal
133+
platform={platformSelected}
134+
currentProject={currentProject}
135+
namespace={modelNamespace}
136+
onClose={onSubmit}
137+
/>
138+
) : null}
139+
</>
140+
);
30141
};

frontend/packages/modelServing/src/components/global/GlobalDeploymentsTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React from 'react';
2-
import ModelServingToolbar from '@odh-dashboard/internal/pages/modelServing/screens/global/ModelServingToolbar';
32
import {
43
initialModelServingFilterData,
54
type ModelServingFilterDataType,
@@ -11,6 +10,7 @@ import { ProjectsContext } from '@odh-dashboard/internal/concepts/projects/Proje
1110
import { useExtensions, useResolvedExtensions } from '@odh-dashboard/plugin-core';
1211
import type { ProjectKind } from '@odh-dashboard/internal/k8sTypes';
1312
import { Label } from '@patternfly/react-core';
13+
import GlobalModelsToolbar from './GlobalModelsToolbar';
1414
import DeploymentsTable from '../deployments/DeploymentsTable';
1515
import {
1616
isModelServingDeploymentsTableExtension,
@@ -107,7 +107,7 @@ const GlobalDeploymentsTable: React.FC<{ deployments: Deployment[]; loaded: bool
107107
loaded={loaded && tableExtensionsLoaded}
108108
platformColumns={platformColumns}
109109
toolbarContent={
110-
<ModelServingToolbar
110+
<GlobalModelsToolbar
111111
filterData={filterData}
112112
onFilterUpdate={(key, value) => setFilterData((prev) => ({ ...prev, [key]: value }))}
113113
/>

frontend/packages/modelServing/src/components/global/GlobalDeploymentsView.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { ProjectObjectType } from '@odh-dashboard/internal/concepts/design/utils
44
import TitleWithIcon from '@odh-dashboard/internal/concepts/design/TitleWithIcon';
55
import type { ProjectKind } from '@odh-dashboard/internal/k8sTypes';
66
import ModelServingLoading from '@odh-dashboard/internal/pages/modelServing/screens/global/ModelServingLoading';
7-
import { useNavigate } from 'react-router-dom';
8-
import { ProjectsContext } from '@odh-dashboard/internal/concepts/projects/ProjectsContext';
7+
import { useNavigate, useParams } from 'react-router-dom';
8+
import { byName, ProjectsContext } from '@odh-dashboard/internal/concepts/projects/ProjectsContext';
99
import { GlobalNoModelsView } from './GlobalNoModelsView';
1010
import GlobalDeploymentsTable from './GlobalDeploymentsTable';
1111
import ModelServingProjectSelection from './ModelServingProjectSelection';
@@ -24,6 +24,9 @@ const GlobalDeploymentsView: React.FC<GlobalDeploymentsViewProps> = ({ projects
2424
const hasDeployments = deployments && deployments.length > 0;
2525
const isLoading = !deploymentsLoaded;
2626
const isEmpty = projects.length === 0 || (!isLoading && !hasDeployments);
27+
const { projects: modelProjects } = React.useContext(ModelDeploymentsContext);
28+
const { namespace: modelNamespace } = useParams<{ namespace: string }>();
29+
const currentProject = modelProjects?.find(byName(modelNamespace));
2730

2831
return (
2932
<ApplicationsPage
@@ -46,7 +49,7 @@ const GlobalDeploymentsView: React.FC<GlobalDeploymentsViewProps> = ({ projects
4649
projects.length === 0 ? (
4750
<NoProjectsPage />
4851
) : (
49-
<GlobalNoModelsView project={preferredProject ?? undefined} />
52+
<GlobalNoModelsView project={currentProject ?? undefined} />
5053
)
5154
}
5255
description="Manage and view the health and performance of your deployed models."
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as React from 'react';
2+
import { SearchInput, ToolbarGroup, ToolbarItem } from '@patternfly/react-core';
3+
import FilterToolbar from '@odh-dashboard/internal/components/FilterToolbar';
4+
import {
5+
ModelServingFilterDataType,
6+
modelServingFilterOptions,
7+
ModelServingToolbarFilterOptions,
8+
} from '@odh-dashboard/internal/pages/modelServing/screens/global/const';
9+
import { DeployButton } from '../deploy/DeployButton';
10+
11+
type GlobalModelsToolbarProps = {
12+
filterData: ModelServingFilterDataType;
13+
onFilterUpdate: (key: string, value?: string | { label: string; value: string }) => void;
14+
};
15+
16+
const GlobalModelsToolbar: React.FC<GlobalModelsToolbarProps> = ({
17+
filterData,
18+
onFilterUpdate,
19+
}) => (
20+
<FilterToolbar<keyof typeof modelServingFilterOptions>
21+
data-testid="model-serving-table-toolbar"
22+
filterOptions={modelServingFilterOptions}
23+
filterOptionRenders={{
24+
[ModelServingToolbarFilterOptions.name]: ({ onChange, ...props }) => (
25+
<SearchInput
26+
{...props}
27+
aria-label="Filter by name"
28+
placeholder="Filter by name"
29+
onChange={(_event, value) => onChange(value)}
30+
/>
31+
),
32+
[ModelServingToolbarFilterOptions.project]: ({ onChange, ...props }) => (
33+
<SearchInput
34+
{...props}
35+
aria-label="Filter by project"
36+
placeholder="Filter by project"
37+
onChange={(_event, value) => onChange(value)}
38+
/>
39+
),
40+
}}
41+
filterData={filterData}
42+
onFilterUpdate={onFilterUpdate}
43+
>
44+
<ToolbarGroup>
45+
<ToolbarItem>
46+
<DeployButton />
47+
</ToolbarItem>
48+
</ToolbarGroup>
49+
</FilterToolbar>
50+
);
51+
52+
export default GlobalModelsToolbar;

frontend/packages/modelServing/src/concepts/useProjectServingPlatform.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const getMultiProjectServingPlatforms = (
5757
};
5858

5959
export const useProjectServingPlatform = (
60-
project: ProjectKind,
60+
project?: ProjectKind,
6161
platforms?: ModelServingPlatform[],
6262
): {
6363
activePlatform?: ModelServingPlatform | null; // This includes preselecting a platform if there is only one
@@ -69,14 +69,14 @@ export const useProjectServingPlatform = (
6969
} => {
7070
const [tmpProjectPlatform, setTmpProjectPlatform] = React.useState<
7171
ModelServingPlatform | null | undefined
72-
>(project.metadata.name && platforms ? getProjectServingPlatform(project, platforms) : undefined);
72+
>(project && platforms ? getProjectServingPlatform(project, platforms) : undefined);
7373
const [projectPlatformError, setProjectPlatformError] = React.useState<string | null>(null);
7474
const [newProjectPlatformLoading, setNewProjectPlatformLoading] = React.useState<
7575
ModelServingPlatform | null | undefined
7676
>();
7777

7878
React.useEffect(() => {
79-
if (!project.metadata.name || !platforms) {
79+
if (!project || !platforms) {
8080
return;
8181
}
8282
const p = getProjectServingPlatform(project, platforms);
@@ -88,6 +88,9 @@ export const useProjectServingPlatform = (
8888

8989
const setProjectPlatform = React.useCallback(
9090
(platformToEnable: ModelServingPlatform) => {
91+
if (!project) {
92+
return;
93+
}
9194
setNewProjectPlatformLoading(platformToEnable);
9295
setProjectPlatformError(null);
9396

@@ -103,6 +106,9 @@ export const useProjectServingPlatform = (
103106
);
104107

105108
const resetProjectPlatform = React.useCallback(() => {
109+
if (!project) {
110+
return;
111+
}
106112
setNewProjectPlatformLoading(null);
107113
setProjectPlatformError(null);
108114

0 commit comments

Comments
 (0)