Skip to content

Commit db11a7e

Browse files
committed
Update MaaS use case for wizard deployment
1 parent d65d464 commit db11a7e

File tree

8 files changed

+107
-15
lines changed

8 files changed

+107
-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,
@@ -413,16 +417,25 @@ describe.skip('MaaS Deployment Wizard', () => {
413417
modelServingWizard.findNextButton().click();
414418

415419
// Focus on MaaS feature testing
416-
// uncheck token auth to simplify test
417-
modelServingWizard.findTokenAuthenticationCheckbox().click();
418-
419420
// Verify MaaS checkbox is unchecked by default
420421
maasWizardField.findSaveAsMaaSCheckbox().should('exist').should('not.be.checked');
421422

422-
// Check the MaaS checkbox
423+
// // Check the MaaS checkbox
424+
modelServingWizard.findSaveAiAssetCheckbox().click();
423425
maasWizardField.findSaveAsMaaSCheckbox().click();
424426
maasWizardField.findSaveAsMaaSCheckbox().should('be.checked');
425427

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

@@ -467,5 +480,44 @@ describe.skip('MaaS Deployment Wizard', () => {
467480

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type MaaSFieldProps = {
2121
id: string;
2222
value: MaaSFieldValue;
2323
onChange: (value: MaaSFieldValue) => void;
24+
isDisabled?: boolean;
2425
};
2526

2627
const MaaSField: React.FC<MaaSFieldProps> = ({ id, value, onChange }) => {
@@ -50,6 +51,7 @@ const MaaSField: React.FC<MaaSFieldProps> = ({ id, value, onChange }) => {
5051
</>
5152
}
5253
isChecked={value.isChecked}
54+
isDisabled={isDisabled}
5355
onChange={handleCheckboxChange}
5456
/>
5557
</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)