Skip to content

Commit 7326f5e

Browse files
committed
Update MaaS use case for wizard deployment
1 parent b46b6a5 commit 7326f5e

File tree

8 files changed

+114
-15
lines changed

8 files changed

+114
-15
lines changed

packages/cypress/cypress/tests/mocked/modelsAsAService/maasTiers.cy.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import {
2222
tierDetailsPage,
2323
tiersPage,
2424
} from '../../../pages/modelsAsAService';
25-
import { modelServingGlobal, modelServingWizard } from '../../../pages/modelServing';
25+
import {
26+
modelServingGlobal,
27+
modelServingSection,
28+
modelServingWizard,
29+
} from '../../../pages/modelServing';
2630
import { mockTier, mockTiers } from '../../../utils/maasUtils';
2731
import {
2832
HardwareProfileModel,
@@ -411,16 +415,25 @@ describe('MaaS Deployment Wizard', () => {
411415
modelServingWizard.findNextButton().click();
412416

413417
// Focus on MaaS feature testing
414-
// uncheck token auth to simplify test
415-
modelServingWizard.findTokenAuthenticationCheckbox().click();
416-
417418
// Verify MaaS checkbox is unchecked by default
418419
maasWizardField.findSaveAsMaaSCheckbox().should('exist').should('not.be.checked');
419420

420-
// Check the MaaS checkbox
421+
// // Check the MaaS checkbox
422+
modelServingWizard.findSaveAiAssetCheckbox().click();
421423
maasWizardField.findSaveAsMaaSCheckbox().click();
422424
maasWizardField.findSaveAsMaaSCheckbox().should('be.checked');
423425

426+
modelServingWizard
427+
.findTokenAuthenticationCheckbox()
428+
.should('not.be.checked')
429+
.should('be.disabled');
430+
modelServingWizard.findSaveAiAssetCheckbox().click();
431+
maasWizardField.findSaveAsMaaSCheckbox().should('not.be.checked').should('be.disabled');
432+
modelServingWizard.findTokenAuthenticationCheckbox().should('be.checked').should('be.enabled');
433+
434+
modelServingWizard.findSaveAiAssetCheckbox().click();
435+
maasWizardField.findSaveAsMaaSCheckbox().click();
436+
424437
// Verify default selection is "All resource tiers"
425438
maasWizardField.findMaaSTierDropdown().should('contain.text', 'All tiers');
426439

@@ -465,5 +478,44 @@ describe('MaaS Deployment Wizard', () => {
465478

466479
cy.wait('@createLLMInferenceService'); // Actual request
467480
cy.get('@createLLMInferenceService.all').should('have.length', 2);
481+
482+
// Re-intercept the list with the newly created MaaS deployment
483+
const maasDeployment = mockLLMInferenceServiceK8sResource({
484+
name: 'test-maas-llmd-model',
485+
namespace: 'test-project',
486+
displayName: 'test-maas-llmd-model',
487+
});
488+
maasDeployment.metadata.annotations = {
489+
...maasDeployment.metadata.annotations,
490+
'alpha.maas.opendatahub.io/tiers': JSON.stringify(['free', 'premium']),
491+
};
492+
maasDeployment.metadata.labels = {
493+
...maasDeployment.metadata.labels,
494+
'opendatahub.io/genai-asset': 'true',
495+
};
496+
497+
cy.interceptK8sList(LLMInferenceServiceModel, mockK8sResourceList([maasDeployment]));
498+
cy.interceptK8sList(InferenceServiceModel, mockK8sResourceList([]));
499+
cy.interceptK8sList(ServingRuntimeModel, mockK8sResourceList([]));
500+
cy.interceptK8s(
501+
{ model: HardwareProfileModel, ns: 'opendatahub', name: 'small-profile' },
502+
mockGlobalScopedHardwareProfiles[0],
503+
);
504+
505+
// Navigate to deployments and verify the expanded row
506+
modelServingSection.visit('test-project');
507+
508+
const row = modelServingSection.getKServeRow('test-maas-llmd-model');
509+
row.findToggleButton('llmd-serving').click();
510+
511+
// Token auth should be hidden when MaaS is enabled
512+
row.findDescriptionListItem('Token authentication').should('not.exist');
513+
514+
// Model availability should show MaaS
515+
row.findDescriptionListItem('Model availability').should('exist');
516+
row
517+
.findDescriptionListItem('Model availability')
518+
.next('dd')
519+
.should('contain.text', 'Model-as-a-Service (MaaS)');
468520
});
469521
});

packages/maas/frontend/src/odh/modelServingExtensions/modelDeploymentWizard/MaaSEndpointCheckbox.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,19 +146,27 @@ type MaasEndpointFieldProps = {
146146
value: MaaSTierValue;
147147
onChange: (value: MaaSTierValue) => void;
148148
externalData?: { data: MaaSEndpointsExternalData; loaded: boolean; loadError?: Error };
149+
isDisabled?: boolean;
149150
};
150151

151152
const MaasEndpointField: React.FC<MaasEndpointFieldProps> = ({
152153
id,
153154
value,
154155
onChange,
155156
externalData,
157+
isDisabled,
156158
}) => {
157159
const { tiers, hasViewTiersPermission } = externalData?.data ?? {
158160
tiers: [],
159161
hasViewTiersPermission: false,
160162
};
161163

164+
React.useEffect(() => {
165+
if (isDisabled && value.isChecked) {
166+
onChange({ isChecked: false });
167+
}
168+
}, [isDisabled, value.isChecked, onChange]);
169+
162170
const tierSelectionOptions = React.useMemo((): SelectionOptions[] => {
163171
const availableTiers = tiers ?? [];
164172
if (hasViewTiersPermission) {
@@ -313,6 +321,7 @@ const MaasEndpointField: React.FC<MaasEndpointFieldProps> = ({
313321
</>
314322
}
315323
isChecked={value.isChecked}
324+
isDisabled={isDisabled}
316325
onChange={handleCheckboxChange}
317326
/>
318327
</Stack>

packages/model-serving/src/components/deploymentWizard/fields/GenericFieldRenderer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ type GenericFieldRendererProps = {
77
wizardState: UseModelDeploymentWizardState;
88
parentId: string;
99
externalData?: ExternalDataMap;
10+
isDisabled?: boolean;
1011
};
1112

1213
export const GenericFieldRenderer: React.FC<GenericFieldRendererProps> = ({
1314
parentId,
1415
wizardState,
1516
externalData,
17+
isDisabled,
1618
}) => {
1719
const fields: WizardField<unknown>[] = React.useMemo(() => {
1820
return wizardState.fields.filter((f) => f.parentId === parentId);
@@ -32,6 +34,7 @@ export const GenericFieldRenderer: React.FC<GenericFieldRendererProps> = ({
3234
});
3335
},
3436
externalData: externalData?.[field.id] ?? undefined,
37+
isDisabled,
3538
})}
3639
</React.Fragment>
3740
))}

packages/model-serving/src/components/deploymentWizard/fields/ModelAvailabilityFields.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,14 @@ export const AvailableAiAssetsFieldsComponent: React.FC<AvailableAiAssetsFieldsC
136136
</div>
137137
</StackItem>
138138
)}
139-
<GenericFieldRenderer
140-
parentId="model-playground-availability"
141-
wizardState={wizardState}
142-
externalData={externalData}
143-
/>
139+
<div style={{ marginLeft: 'var(--pf-t--global--spacer--lg)' }}>
140+
<GenericFieldRenderer
141+
parentId="model-playground-availability"
142+
wizardState={wizardState}
143+
externalData={externalData}
144+
isDisabled={!data.saveAsAiAsset}
145+
/>
146+
</div>
144147
</Stack>
145148
</StackItem>
146149
);

packages/model-serving/src/components/deploymentWizard/fields/TokenAuthenticationField.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,35 @@ type TokenAuthenticationFieldProps = {
189189
tokens?: TokenAuthenticationFieldData;
190190
onChange?: TokenAuthenticationFieldHook['setData'];
191191
allowCreate?: boolean;
192+
forceDisabled?: boolean;
192193
};
193194

194195
export const TokenAuthenticationField: React.FC<TokenAuthenticationFieldProps> = ({
195196
tokens = [],
196197
onChange,
197198
allowCreate = false,
199+
forceDisabled = false,
198200
}) => {
201+
const savedTokensRef = React.useRef<TokenAuthenticationFieldData | null>(null);
202+
const wasForceDisabledRef = React.useRef(forceDisabled);
203+
204+
React.useEffect(() => {
205+
if (forceDisabled && tokens.length > 0) {
206+
savedTokensRef.current = tokens;
207+
onChange?.([]);
208+
}
209+
if (!forceDisabled && wasForceDisabledRef.current) {
210+
if (savedTokensRef.current) {
211+
onChange?.(savedTokensRef.current);
212+
} else {
213+
onChange?.([{ uuid: getUniqueId('ml'), displayName: 'default-name', error: '' }]);
214+
}
215+
savedTokensRef.current = null;
216+
}
217+
wasForceDisabledRef.current = forceDisabled;
218+
}, [forceDisabled, tokens, onChange]);
219+
220+
const isDisabled = forceDisabled || !allowCreate;
199221
const createNewToken = React.useCallback(() => {
200222
const displayName = 'default-name';
201223
const duplicated = tokens.filter((token) => token.displayName === displayName);
@@ -227,7 +249,7 @@ export const TokenAuthenticationField: React.FC<TokenAuthenticationFieldProps> =
227249
id="alt-form-checkbox-auth"
228250
data-testid="token-authentication-checkbox"
229251
name="alt-form-checkbox-auth"
230-
isDisabled={!allowCreate}
252+
isDisabled={isDisabled}
231253
isChecked={tokens.length > 0}
232254
onChange={(e, check) => {
233255
if (check && tokens.length === 0) {
@@ -242,7 +264,7 @@ export const TokenAuthenticationField: React.FC<TokenAuthenticationFieldProps> =
242264
<StackItem>
243265
<div style={{ marginLeft: 'var(--pf-t--global--spacer--lg)' }}>
244266
<Stack hasGutter>
245-
{allowCreate && (
267+
{!isDisabled && (
246268
<StackItem>
247269
<Alert
248270
variant="info"
@@ -257,7 +279,7 @@ export const TokenAuthenticationField: React.FC<TokenAuthenticationFieldProps> =
257279
newToken={token}
258280
existingTokens={tokens}
259281
setTokens={onChange}
260-
disabled={!allowCreate}
282+
disabled={isDisabled}
261283
/>
262284
</StackItem>
263285
))}
@@ -268,7 +290,7 @@ export const TokenAuthenticationField: React.FC<TokenAuthenticationFieldProps> =
268290
iconPosition="left"
269291
variant="link"
270292
icon={<PlusCircleIcon />}
271-
isDisabled={!allowCreate}
293+
isDisabled={isDisabled}
272294
data-testid="add-service-account-button"
273295
>
274296
Add a service account

packages/model-serving/src/components/deploymentWizard/steps/AdvancedOptionsStep.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ export const AdvancedSettingsStepContent: React.FC<AdvancedSettingsStepContentPr
4646
const tokenAuthData = wizardState.state.tokenAuthentication.data;
4747
const { isExternalRouteVisible, shouldAutoCheckTokens } = wizardState.advancedOptions;
4848

49+
const isMaaSChecked = (() => {
50+
const field = wizardState.state['maas/save-as-maas-checkbox'];
51+
if (typeof field === 'object' && field !== null && 'isChecked' in field) {
52+
return !!field.isChecked;
53+
}
54+
return false;
55+
})();
56+
4957
// TODO: Clean up the stuff below related to KServe. Maybe move to an extension?
5058
const selectedModelServer = React.useMemo(() => {
5159
const templates = wizardState.state.modelFormatState.templatesFilteredForModelType;
@@ -153,6 +161,7 @@ export const AdvancedSettingsStepContent: React.FC<AdvancedSettingsStepContentPr
153161
<TokenAuthenticationField
154162
tokens={tokenAuthData}
155163
allowCreate={allowCreate}
164+
forceDisabled={isMaaSChecked}
156165
onChange={wizardState.state.tokenAuthentication.setData}
157166
/>
158167
</FormGroup>

packages/model-serving/src/components/deploymentWizard/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export type WizardField<
219219
value: FieldData;
220220
onChange: (value: FieldData) => void;
221221
externalData?: { data: ExternalData; loaded: boolean; loadError?: Error };
222+
isDisabled?: boolean;
222223
}>;
223224
getReviewSections?: (
224225
value: FieldData,

packages/model-serving/src/components/deployments/row/DeploymentsTableRowExpandedSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export const DeploymentRowExpandedSection: React.FC<{
199199
hardwareProfile={hardwareProfileOptions}
200200
/>
201201
{modelAvailability && <ModelAvailabilityItem modelAvailability={modelAvailability} />}
202-
<TokenAuthenticationItem deployment={deployment} />
202+
{!modelAvailability?.saveAsMaaS && <TokenAuthenticationItem deployment={deployment} />}
203203
</Stack>
204204
</ExpandableRowContent>
205205
</Td>

0 commit comments

Comments
 (0)