Skip to content

Commit 807be24

Browse files
committed
Add form/yaml toggle with basic yaml viewer
1 parent a113e4e commit 807be24

File tree

5 files changed

+155
-10
lines changed

5 files changed

+155
-10
lines changed

packages/cypress/cypress/pages/modelServing.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,14 @@ class ModelServingWizard extends Wizard {
13261326
findReviewStepModelDetailsSection() {
13271327
return cy.findByTestId('review-step-model-details');
13281328
}
1329+
1330+
findYAMLViewerToggle(name: string) {
1331+
return cy.findByRole('button', { name });
1332+
}
1333+
1334+
findYAMLCodeEditor() {
1335+
return cy.findByTestId('yaml-editor');
1336+
}
13291337
}
13301338

13311339
export const modelServingGlobal = new ModelServingGlobal();

packages/cypress/cypress/tests/mocked/modelServing/modelServingDeploy.cy.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const initIntercepts = ({
6969
mockDashboardConfig({
7070
disableNIMModelServing: true,
7171
disableKServe: false,
72+
deploymentWizardYAMLViewer: true,
7273
}),
7374
);
7475
// used by addSupportServingPlatformProject
@@ -1691,6 +1692,48 @@ describe('Model Serving Deploy Wizard', () => {
16911692
modelServingWizardEdit.findModelSourceStep().should('be.enabled');
16921693
});
16931694

1695+
it('Should show YAML preview mode when LLMd is selected', () => {
1696+
initIntercepts({ modelType: ServingRuntimeModelType.GENERATIVE });
1697+
cy.interceptK8sList(
1698+
{ model: InferenceServiceModel, ns: 'test-project' },
1699+
mockK8sResourceList([mockInferenceServiceK8sResource({})]),
1700+
);
1701+
cy.interceptK8sList(
1702+
{ model: ServingRuntimeModel, ns: 'test-project' },
1703+
mockK8sResourceList([mockServingRuntimeK8sResource({})]),
1704+
);
1705+
1706+
modelServingGlobal.visit('test-project');
1707+
modelServingGlobal.findDeployModelButton().click();
1708+
modelServingWizard.findModelTypeSelectOption(ModelTypeLabel.GENERATIVE).should('exist').click();
1709+
modelServingWizard.findModelLocationSelect().should('exist');
1710+
modelServingWizard
1711+
.findModelLocationSelectOption(ModelLocationSelectOption.EXISTING)
1712+
.should('exist')
1713+
.click();
1714+
modelServingWizard.findExistingConnectionSelect().should('exist').click();
1715+
modelServingWizard
1716+
.findExistingConnectionSelectOption('Test URI Secret')
1717+
.should('exist')
1718+
.click();
1719+
modelServingWizard.findNextButton().should('be.enabled').click();
1720+
modelServingWizard.findModelDeploymentNameInput().type('test-model');
1721+
modelServingWizard.findServingRuntimeTemplateSearchSelector().click();
1722+
modelServingWizard
1723+
.findGlobalScopedTemplateOption('Distributed inference with llm-d')
1724+
.should('exist')
1725+
.click();
1726+
1727+
// YAML Viewer
1728+
modelServingWizard.findYAMLViewerToggle('YAML').should('exist').click();
1729+
modelServingWizard.findYAMLCodeEditor().should('exist');
1730+
1731+
modelServingWizard.findYAMLViewerToggle('Form').should('exist').click();
1732+
modelServingWizard.findServingRuntimeTemplateSearchSelector().click();
1733+
modelServingWizard.findGlobalScopedTemplateOption('vLLM NVIDIA').should('exist').click();
1734+
modelServingWizard.findYAMLViewerToggle('YAML').should('not.exist');
1735+
});
1736+
16941737
describe('redirect from v2 to v3 route', () => {
16951738
beforeEach(() => {
16961739
initIntercepts({});

packages/model-serving/src/components/deploymentWizard/ModelDeploymentWizard.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { InitialWizardFormData, WizardStepTitle } from './types';
2222
import { ExitDeploymentModal } from './exitModal/ExitDeploymentModal';
2323
import { useRefreshWizardPage } from './useRefreshWizardPage';
2424
import { useExitDeploymentWizard } from './exitModal/useExitDeploymentWizard';
25+
import { DeploymentWizardYAMLView } from './useDeploymentWizardYAMLView';
2526
import { WizardFooterWithDisablingNext } from '../generic/WizardFooterWithDisablingNext';
2627

2728
type ModelDeploymentWizardProps = {
@@ -33,6 +34,9 @@ type ModelDeploymentWizardProps = {
3334
existingDeployment?: Deployment;
3435
returnRoute?: string;
3536
cancelReturnRoute?: string;
37+
headerAction?: React.ReactNode;
38+
viewMode?: 'form' | 'yaml-preview' | 'yaml-edit';
39+
onModelServerChange?: (modelServerName: string | undefined) => void;
3640
};
3741

3842
const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
@@ -44,6 +48,9 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
4448
existingDeployment,
4549
returnRoute,
4650
cancelReturnRoute,
51+
headerAction,
52+
viewMode = 'form',
53+
onModelServerChange,
4754
}) => {
4855
const onRefresh = useRefreshWizardPage(existingDeployment);
4956
const { isExitModalOpen, openExitModal, closeExitModal, handleExitConfirm, exitWizardOnSubmit } =
@@ -56,6 +63,12 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
5663
const validation = useModelDeploymentWizardValidation(wizardState.state, wizardState.fields);
5764
const currentProjectName = wizardState.state.project.projectName ?? undefined;
5865

66+
React.useEffect(() => {
67+
if (onModelServerChange) {
68+
onModelServerChange(wizardState.state.modelServer.data?.name);
69+
}
70+
}, [wizardState.state.modelServer.data?.name, onModelServerChange]);
71+
5972
const { deployMethod, deployMethodLoaded } = useDeployMethod(wizardState.state);
6073
// TODO in same jira, replace deployMethod with applyFieldData for all other fields
6174
const { applyFieldData, applyExtensionsLoaded } = useWizardFieldApply(wizardState.state);
@@ -194,7 +207,13 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
194207
}
195208
`}
196209
</style>
197-
<ApplicationsPage title={title} description={description} loaded empty={false}>
210+
<ApplicationsPage
211+
title={title}
212+
description={description}
213+
loaded
214+
empty={false}
215+
headerAction={headerAction}
216+
>
198217
<ExternalDataLoader
199218
fields={wizardState.fields}
200219
initialData={existingData}
@@ -210,7 +229,9 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
210229
startIndex={wizardState.initialData?.wizardStartIndex ?? 1}
211230
>
212231
<WizardStep name={WizardStepTitle.MODEL_DETAILS} id="source-model-step">
213-
{wizardState.loaded.modelSourceLoaded ? (
232+
{viewMode === 'yaml-preview' ? (
233+
<DeploymentWizardYAMLView />
234+
) : wizardState.loaded.modelSourceLoaded ? (
214235
<ModelSourceStepContent
215236
wizardState={wizardState}
216237
validation={validation.modelSource}
@@ -222,9 +243,11 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
222243
<WizardStep
223244
name={WizardStepTitle.MODEL_DEPLOYMENT}
224245
id="model-deployment-step"
225-
isDisabled={!validation.isModelSourceStepValid}
246+
isDisabled={viewMode === 'form' && !validation.isModelSourceStepValid}
226247
>
227-
{wizardState.loaded.modelDeploymentLoaded ? (
248+
{viewMode === 'yaml-preview' ? (
249+
<DeploymentWizardYAMLView />
250+
) : wizardState.loaded.modelDeploymentLoaded ? (
228251
<ModelDeploymentStepContent
229252
projectName={currentProjectName}
230253
wizardState={wizardState}
@@ -237,10 +260,13 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
237260
name={WizardStepTitle.ADVANCED_SETTINGS}
238261
id="advanced-options-step"
239262
isDisabled={
240-
!validation.isModelSourceStepValid || !validation.isModelDeploymentStepValid
263+
viewMode === 'form' &&
264+
(!validation.isModelSourceStepValid || !validation.isModelDeploymentStepValid)
241265
}
242266
>
243-
{wizardState.loaded.advancedOptionsLoaded ? (
267+
{viewMode === 'yaml-preview' ? (
268+
<DeploymentWizardYAMLView />
269+
) : wizardState.loaded.advancedOptionsLoaded ? (
244270
<AdvancedSettingsStepContent
245271
wizardState={wizardState}
246272
projectName={currentProjectName}
@@ -254,12 +280,15 @@ const ModelDeploymentWizard: React.FC<ModelDeploymentWizardProps> = ({
254280
name={WizardStepTitle.REVIEW}
255281
id="summary-step"
256282
isDisabled={
257-
!validation.isModelSourceStepValid ||
258-
!validation.isModelDeploymentStepValid ||
259-
!validation.isAdvancedSettingsStepValid
283+
viewMode === 'form' &&
284+
(!validation.isModelSourceStepValid ||
285+
!validation.isModelDeploymentStepValid ||
286+
!validation.isAdvancedSettingsStepValid)
260287
}
261288
>
262-
{wizardState.loaded.summaryLoaded ? (
289+
{viewMode === 'yaml-preview' ? (
290+
<DeploymentWizardYAMLView />
291+
) : wizardState.loaded.summaryLoaded ? (
263292
<ReviewStepContent
264293
wizardState={wizardState}
265294
projectName={currentProjectName}

packages/model-serving/src/components/deploymentWizard/ModelDeploymentWizardPage.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import {
99
EmptyStateBody,
1010
EmptyState,
1111
Spinner,
12+
ToggleGroup,
13+
ToggleGroupItem,
1214
} from '@patternfly/react-core';
1315
import { ExclamationCircleIcon } from '@patternfly/react-icons';
16+
import { SupportedArea, useIsAreaAvailable } from '@odh-dashboard/internal/concepts/areas';
1417
import ModelDeploymentWizard from './ModelDeploymentWizard';
1518
import { ModelDeploymentsProvider } from '../../concepts/ModelDeploymentsContext';
1619
import { useAvailableClusterPlatforms } from '../../concepts/useAvailableClusterPlatforms';
@@ -38,8 +41,14 @@ const ErrorContent: React.FC<{ error: Error }> = ({ error }) => {
3841
);
3942
};
4043

44+
const LLMD_SERVING_ID = 'llmd-serving';
45+
4146
export const ModelDeploymentWizardPage: React.FC = () => {
4247
const location = useLocation();
48+
const [viewMode, setViewMode] = React.useState<'form' | 'yaml-preview' | 'yaml-edit'>('form');
49+
const [selectedModelServer, setSelectedModelServer] = React.useState<string | undefined>();
50+
const isYAMLViewerEnabled = useIsAreaAvailable(SupportedArea.YAML_VIEWER).status;
51+
const isLLMdSelected = selectedModelServer === LLMD_SERVING_ID;
4352

4453
// Extract state from navigation
4554
const existingData = location.state?.initialData;
@@ -99,6 +108,28 @@ export const ModelDeploymentWizardPage: React.FC = () => {
99108
existingDeployment={existingDeployment}
100109
returnRoute={returnRoute}
101110
cancelReturnRoute={cancelReturnRoute}
111+
viewMode={viewMode}
112+
onModelServerChange={setSelectedModelServer}
113+
headerAction={
114+
isYAMLViewerEnabled && isLLMdSelected ? (
115+
<ToggleGroup aria-label="Deployment view mode">
116+
<ToggleGroupItem
117+
data-testid="form-view"
118+
text="Form"
119+
buttonId="form-view"
120+
isSelected={viewMode === 'form'}
121+
onChange={() => setViewMode('form')}
122+
/>
123+
<ToggleGroupItem
124+
data-testid="yaml-view"
125+
text="YAML"
126+
buttonId="yaml-view"
127+
isSelected={viewMode === 'yaml-preview'}
128+
onChange={() => setViewMode('yaml-preview')}
129+
/>
130+
</ToggleGroup>
131+
) : undefined
132+
}
102133
/>
103134
</ModelDeploymentsProvider>
104135
);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import { Language } from '@patternfly/react-code-editor';
3+
import { Stack, StackItem, Flex, FlexItem, Button } from '@patternfly/react-core';
4+
import DashboardCodeEditor from '@odh-dashboard/internal/concepts/dashboard/codeEditor/DashboardCodeEditor';
5+
6+
export const DeploymentWizardYAMLView: React.FC = () => {
7+
const code =
8+
'# Continue filling out the form to preview YAML, or enter Manual Edit Mode to write YAML directly\n';
9+
10+
return (
11+
<Stack hasGutter>
12+
<StackItem>
13+
<Flex justifyContent={{ default: 'justifyContentFlexEnd' }}>
14+
<FlexItem>
15+
<Button variant="primary" data-testid="manual-edit-mode-button">
16+
Enter Manual Edit Mode
17+
</Button>
18+
</FlexItem>
19+
</Flex>
20+
</StackItem>
21+
<StackItem>
22+
<DashboardCodeEditor
23+
testId="yaml-editor"
24+
code={code}
25+
language={Language.yaml}
26+
isReadOnly
27+
isLineNumbersVisible
28+
isLanguageLabelVisible
29+
codeEditorHeight="500px"
30+
/>
31+
</StackItem>
32+
</Stack>
33+
);
34+
};

0 commit comments

Comments
 (0)