diff --git a/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts b/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts index 1baf764587..898759e54b 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/modelServing.ts @@ -177,6 +177,10 @@ class ServingRuntimeModal extends Modal { return this.find().findByLabelText('Model server size'); } + findServingRuntimeTemplateHelptext() { + return this.find().findByTestId('serving-runtime-template-helptext'); + } + findServingRuntimeTemplateDropdown() { return this.find().findByTestId('serving-runtime-template-selection'); } diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts index 56e6b606f1..cd2a4afd6b 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts @@ -14,6 +14,7 @@ import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/Delete import { inferenceServiceModal, inferenceServiceModalEdit, + kserveModal, modelServingGlobal, } from '~/__tests__/cypress/cypress/pages/modelServing'; import { @@ -38,6 +39,7 @@ type HandlersProps = { delayInferenceServices?: boolean; delayServingRuntimes?: boolean; disableKServeMetrics?: boolean; + disableServingRuntimeParamsConfig?: boolean; }; const initIntercepts = ({ @@ -49,11 +51,15 @@ const initIntercepts = ({ delayInferenceServices, delayServingRuntimes, disableKServeMetrics, + disableServingRuntimeParamsConfig, }: HandlersProps) => { cy.interceptOdh( 'GET /api/dsc/status', mockDscStatus({ - installedComponents: { kserve: true, 'model-mesh': true }, + installedComponents: { + kserve: true, + 'model-mesh': true, + }, }), ); cy.interceptOdh( @@ -62,6 +68,7 @@ const initIntercepts = ({ disableKServe: disableKServeConfig, disableModelMesh: disableModelMeshConfig, disableKServeMetrics, + disableServingRuntimeParams: disableServingRuntimeParamsConfig, }), ); cy.interceptK8sList(ServingRuntimeModel, mockK8sResourceList(servingRuntimes)); @@ -490,6 +497,23 @@ describe('Model Serving Global', () => { modelServingGlobal.findDeployModelButton().click(); cy.findByText('Error creating model server').should('not.exist'); }); + + it('Serving runtime helptext', () => { + initIntercepts({ + projectEnableModelMesh: false, + disableServingRuntimeParamsConfig: false, + }); + modelServingGlobal.visit('test-project'); + + modelServingGlobal.findDeployModelButton().click(); + + // test that you can not submit on empty + kserveModal.shouldBeOpen(); + kserveModal.findServingRuntimeTemplateHelptext().should('not.exist'); + kserveModal.findServingRuntimeTemplateDropdown().findSelectOption('Caikit').click(); + kserveModal.findServingRuntimeTemplateHelptext().should('exist'); + }); + it('Navigate to kserve model metrics page only if enabled', () => { initIntercepts({}); modelServingGlobal.visit('test-project'); diff --git a/frontend/src/pages/modelServing/screens/projects/ServingRuntimeModal/ServingRuntimeTemplateSection.tsx b/frontend/src/pages/modelServing/screens/projects/ServingRuntimeModal/ServingRuntimeTemplateSection.tsx index ded7b4b7e6..14698321a1 100644 --- a/frontend/src/pages/modelServing/screens/projects/ServingRuntimeModal/ServingRuntimeTemplateSection.tsx +++ b/frontend/src/pages/modelServing/screens/projects/ServingRuntimeModal/ServingRuntimeTemplateSection.tsx @@ -1,5 +1,15 @@ import * as React from 'react'; -import { FormGroup, Label, Split, SplitItem, Truncate } from '@patternfly/react-core'; +import { + Button, + FormGroup, + FormHelperText, + HelperText, + HelperTextItem, + Label, + Split, + SplitItem, + Truncate, +} from '@patternfly/react-core'; import { UpdateObjectAtPropAndValue } from '~/pages/projects/types'; import { CreatingServingRuntimeObject } from '~/pages/modelServing/screens/types'; import { TemplateKind } from '~/k8sTypes'; @@ -14,6 +24,7 @@ import { AcceleratorProfileFormData } from '~/utilities/useAcceleratorProfileFor type ServingRuntimeTemplateSectionProps = { data: CreatingServingRuntimeObject; + onConfigureParamsClick?: () => void; setData: UpdateObjectAtPropAndValue; templates: TemplateKind[]; isEditing?: boolean; @@ -22,6 +33,7 @@ type ServingRuntimeTemplateSectionProps = { const ServingRuntimeTemplateSection: React.FC = ({ data, + onConfigureParamsClick, setData, templates, isEditing, @@ -77,6 +89,19 @@ const ServingRuntimeTemplateSection: React.FC + {data.servingRuntimeTemplateName && onConfigureParamsClick && ( + + + + You can optimize model performance by{' '} + {' '} + of the selected serving runtime. + + + + )} ); }; diff --git a/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx b/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx index 79e0737e14..c1c71c57e3 100644 --- a/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx +++ b/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx @@ -215,6 +215,8 @@ const ManageKServeModal: React.FC = ({ [editInfo, servingRuntimeTemplates, createDataServingRuntime.servingRuntimeTemplateName], ); + const servingRuntimeArgsInputRef = React.useRef(null); + const onBeforeClose = (submitted: boolean) => { fireFormTrackingEvent(editInfo ? 'Model Updated' : 'Model Deployed', { outcome: TrackingOutcome.cancel, @@ -377,6 +379,14 @@ const ManageKServeModal: React.FC = ({ /> + requestAnimationFrame(() => { + servingRuntimeArgsInputRef.current?.focus(); + }) + : undefined + } setData={setCreateDataServingRuntime} templates={servingRuntimeTemplates || []} isEditing={!!editInfo} @@ -440,6 +450,7 @@ const ManageKServeModal: React.FC = ({ ; + inputRef: React.RefObject; }; -const ServingRuntimeArgsSection: React.FC = ({ data, setData }) => ( +const ServingRuntimeArgsSection: React.FC = ({ + data, + setData, + inputRef, +}) => ( = ({ da fieldId="serving-runtime-arguments" >