Skip to content

Commit 9d37418

Browse files
committed
feat(FR-2891): move resource group to deployment-level metadata and refine deployment detail UI
1 parent 6afc3c2 commit 9d37418

30 files changed

Lines changed: 446 additions & 184 deletions

data/schema.graphql

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,10 @@ input AddRevisionInput
158158
"""
159159
revisionPresetId: UUID = null
160160
deploymentId: ID!
161-
clusterConfig: ClusterConfigInput!
162-
resourceConfig: ResourceConfigInput!
163-
image: ImageInput!
164-
modelRuntimeConfig: ModelRuntimeConfigInput!
161+
clusterConfig: ClusterConfigInput = null
162+
resourceConfig: ResourceConfigInput = null
163+
image: ImageInput = null
164+
modelRuntimeConfig: ModelRuntimeConfigInput = null
165165
modelMountConfig: ModelMountConfigInput!
166166

167167
"""
@@ -9380,6 +9380,7 @@ input ModelDeploymentMetadataInput
93809380
{
93819381
projectId: ID!
93829382
domainName: String!
9383+
resourceGroup: String!
93839384
name: String = null
93849385
tags: [String!] = null
93859386
}
@@ -14893,8 +14894,6 @@ type ResourceConfig
1489314894
input ResourceConfigInput
1489414895
@join__type(graph: STRAWBERRY)
1489514896
{
14896-
resourceGroup: ResourceGroupInput!
14897-
1489814897
"""Added in 26.1.0. Resources allocated for the deployment."""
1489914898
resourceSlots: ResourceSlotInput!
1490014899

@@ -14998,13 +14997,6 @@ input ResourceGroupFilter
1499814997
NOT: [ResourceGroupFilter!] = null
1499914998
}
1500014999

15001-
"""Added in 25.19.0. """
15002-
input ResourceGroupInput
15003-
@join__type(graph: STRAWBERRY)
15004-
{
15005-
name: String!
15006-
}
15007-
1500815000
"""Added in 26.2.0. Metadata for a resource group."""
1500915001
type ResourceGroupMetadata
1501015002
@join__type(graph: STRAWBERRY)
@@ -20590,4 +20582,4 @@ input WebhookSpecInput
2059020582
@join__type(graph: STRAWBERRY)
2059120583
{
2059220584
url: String!
20593-
}
20585+
}

react/src/components/DeploymentAddRevisionModal.tsx

Lines changed: 65 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
Copyright (c) 2015-2026 Lablup Inc. All rights reserved.
44
*/
55
import { DeploymentAddRevisionModalAddMutation } from '../__generated__/DeploymentAddRevisionModalAddMutation.graphql';
6-
import { DeploymentAddRevisionModalDeployMutation } from '../__generated__/DeploymentAddRevisionModalDeployMutation.graphql';
76
import { DeploymentAddRevisionModalImageNameQuery } from '../__generated__/DeploymentAddRevisionModalImageNameQuery.graphql';
87
import type { DeploymentAddRevisionModalPresetTransferFragment$key } from '../__generated__/DeploymentAddRevisionModalPresetTransferFragment.graphql';
98
import type { DeploymentAddRevisionModalQuery } from '../__generated__/DeploymentAddRevisionModalQuery.graphql';
@@ -17,9 +16,7 @@ import {
1716
mergeExtraArgs,
1817
reverseMapExtraArgs,
1918
} from '../helper/runtimeExtraArgsParser';
20-
import { useWebUINavigate } from '../hooks';
2119
import { useBAISettingUserState } from '../hooks/useBAISetting';
22-
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
2320
import { useModelStoreProject } from '../hooks/useModelStoreProject';
2421
import {
2522
buildArgsSchemaKeySet,
@@ -70,7 +67,6 @@ import {
7067
BAIFlex,
7168
BAIModal,
7269
BAIModalProps,
73-
BAIProjectResourceGroupSelect,
7470
BAIProjectVfolderSelect,
7571
BAIRuntimeVariantSelect,
7672
convertToUUID,
@@ -118,7 +114,6 @@ export type FormValues = ImageEnvironmentFormInput &
118114

119115
export type PresetFormValues = {
120116
revisionPresetId: string;
121-
resourceGroup: string;
122117
modelFolderId: string;
123118
};
124119

@@ -187,9 +182,7 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
187182
const { t } = useTranslation();
188183
const { token } = theme.useToken();
189184
const { message } = App.useApp();
190-
const navigate = useWebUINavigate();
191185
const relayEnvironment = useRelayEnvironment();
192-
const { id: projectId, name: projectName } = useCurrentProjectValue();
193186
// The model folder picker scopes to the MODEL_STORE project, not the
194187
// deployment's own project — model cards live in the domain-wide model
195188
// store regardless of which project owns the deployment.
@@ -206,8 +199,8 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
206199
const [presetForm] = Form.useForm<PresetFormValues>();
207200
const [isSubmitting, setIsSubmitting] = useState(false);
208201
// FR-2862 feedback: hoist `autoActivate` from the Custom body into the
209-
// modal so it can be rendered in the modal footer (visible only in Custom
210-
// mode, since Preset mode always auto-activates server-side).
202+
// modal so it can be rendered in the modal footer. Both modes forward
203+
// the value via `AddRevisionOptions.autoActivate` on `addModelRevision`.
211204
const [autoActivate, setAutoActivate] = useState(true);
212205

213206
const [mode, setMode] = useBAISettingUserState(
@@ -463,20 +456,6 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
463456
`,
464457
);
465458

466-
const [commitDeploy] = useMutation<DeploymentAddRevisionModalDeployMutation>(
467-
graphql`
468-
mutation DeploymentAddRevisionModalDeployMutation(
469-
$vfolderId: UUID!
470-
$input: DeployVFolderV2Input!
471-
) {
472-
deployVfolderV2(vfolderId: $vfolderId, input: $input) {
473-
deploymentId
474-
deploymentName
475-
}
476-
}
477-
`,
478-
);
479-
480459
// Build a Custom-form prefill object from a preset's transfer-fragment ref.
481460
// `image canonicalName` is fetched async because
482461
// `ImageEnvironmentSelectFormItems` matches the form's `environments.version`
@@ -575,16 +554,12 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
575554

576555
if (effectiveMode === 'preset' && next === 'custom') {
577556
// Carry the currently selected preset (if any) into the Custom form.
578-
// Also carry the resource group / model folder the user picked in
579-
// Preset mode (spec (d)).
557+
// Also carry the model folder the user picked in Preset mode (spec (d)).
580558
const presetValues = presetForm.getFieldsValue();
581559
let prefill: Partial<FormValues> = {};
582560
if (selectedPresetFrgmt) {
583561
prefill = await buildPrefillFromPreset(selectedPresetFrgmt);
584562
}
585-
if (presetValues.resourceGroup) {
586-
prefill.resourceGroup = presetValues.resourceGroup;
587-
}
588563
if (presetValues.modelFolderId) {
589564
prefill.modelFolderId = presetValues.modelFolderId;
590565
}
@@ -977,7 +952,6 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
977952
size: values.cluster_size,
978953
},
979954
resourceConfig: {
980-
resourceGroup: { name: values.resourceGroup },
981955
resourceSlots: { entries: slotEntries },
982956
resourceOpts:
983957
optsEntries.length > 0 ? { entries: optsEntries } : null,
@@ -1031,48 +1005,59 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
10311005
};
10321006

10331007
const handlePresetFinish = (values: PresetFormValues): void => {
1034-
if (!projectId) {
1035-
message.error(t('general.ErrorOccurred'));
1036-
return;
1037-
}
10381008
setIsSubmitting(true);
1039-
commitDeploy({
1009+
// Preset mode adds a revision to the current deployment using the
1010+
// selected `revisionPresetId`. Cluster / resource / image / runtime
1011+
// configs are derived server-side from the preset; the client only
1012+
// forwards the user-picked model folder via `modelMountConfig` and the
1013+
// `autoActivate` option.
1014+
commitAdd({
10401015
variables: {
1041-
vfolderId: convertToUUID(values.modelFolderId),
10421016
input: {
1043-
projectId,
1017+
deploymentId: toLocalId(deploymentId) ?? deploymentId,
10441018
revisionPresetId: values.revisionPresetId,
1045-
resourceGroup: values.resourceGroup,
1046-
desiredReplicaCount: 1,
1019+
modelMountConfig: {
1020+
vfolderId: convertToUUID(values.modelFolderId),
1021+
mountDestination: '/models',
1022+
},
1023+
options: { autoActivate },
10471024
},
10481025
},
1049-
onCompleted: (response, errors) => {
1026+
onCompleted: (_, errors) => {
10501027
setIsSubmitting(false);
10511028
if (errors && errors.length > 0) {
1052-
const errMsg = errors.map((e) => e.message).join('\n');
1029+
const err = errors[0];
1030+
const isInProgress = err?.message?.includes(
1031+
'Another deployment is already in progress',
1032+
);
10531033
logger.error(
1054-
'[DeploymentAddRevisionModal] deployVfolderV2 returned errors',
1034+
'[DeploymentAddRevisionModal] addModelRevision (preset) returned errors',
10551035
errors,
10561036
);
1057-
message.error(errMsg || t('modelStore.DeployFailed'));
1037+
message.error(
1038+
isInProgress
1039+
? t('deployment.AnotherDeploymentInProgress')
1040+
: (err?.message ?? t('general.ErrorOccurred')),
1041+
);
10581042
return;
10591043
}
1060-
const newDeploymentId = response.deployVfolderV2?.deploymentId
1061-
? String(response.deployVfolderV2.deploymentId)
1062-
: undefined;
1063-
message.success(t('modelStore.DeploySuccess'));
1044+
presetForm.resetFields();
10641045
onRequestClose(true);
1065-
if (newDeploymentId) {
1066-
navigate(`/deployments/${newDeploymentId}`);
1067-
}
10681046
},
10691047
onError: (error) => {
10701048
setIsSubmitting(false);
1049+
const isInProgress = error.message?.includes(
1050+
'Another deployment is already in progress',
1051+
);
10711052
logger.error(
1072-
'[DeploymentAddRevisionModal] deployVfolderV2 failed',
1053+
'[DeploymentAddRevisionModal] addModelRevision (preset) failed',
10731054
error,
10741055
);
1075-
message.error(error.message || t('modelStore.DeployFailed'));
1056+
message.error(
1057+
isInProgress
1058+
? t('deployment.AnotherDeploymentInProgress')
1059+
: (error.message ?? t('general.ErrorOccurred')),
1060+
);
10761061
},
10771062
});
10781063
};
@@ -1099,16 +1084,13 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
10991084
// success → `onFinish` (handle*Finish), failure → `onFinishFailed`
11001085
// (handleFinishFailed). Calling `validateFields()` separately and
11011086
// swallowing rejections in a `.catch` would silently drop submits
1102-
// when validation fails — including transient races during the
1103-
// BAIProjectResourceGroupSelect auto-select.
1087+
// when validation fails — including transient races during
1088+
// hidden-picker auto-select flows.
11041089
const activeForm = effectiveMode === 'preset' ? presetForm : customForm;
11051090
activeForm.submit();
11061091
};
11071092

1108-
const okText =
1109-
effectiveMode === 'preset'
1110-
? t('modelStore.Deploy')
1111-
: t('deployment.AddRevision');
1093+
const okText = t('deployment.AddRevision');
11121094

11131095
return (
11141096
<BAIModal
@@ -1137,18 +1119,15 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
11371119
width={720}
11381120
footer={
11391121
<BAIFlex direction="row" align="center" justify="between" gap="sm">
1140-
{effectiveMode === 'custom' ? (
1141-
<Checkbox
1142-
checked={autoActivate}
1143-
onChange={(e: CheckboxChangeEvent) =>
1144-
setAutoActivate(e.target.checked)
1145-
}
1146-
>
1147-
{t('deployment.AutoActivate')}
1148-
</Checkbox>
1149-
) : (
1150-
<span />
1151-
)}
1122+
<Checkbox
1123+
checked={autoActivate}
1124+
onChange={(e: CheckboxChangeEvent) =>
1125+
setAutoActivate(e.target.checked)
1126+
}
1127+
disabled={effectiveMode === 'preset' && hasNoPresets}
1128+
>
1129+
{t('deployment.AutoActivate')}
1130+
</Checkbox>
11521131
<BAIFlex direction="row" align="center" gap="xs">
11531132
<Button onClick={() => onRequestClose()}>
11541133
{t('button.Cancel')}
@@ -1233,19 +1212,6 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
12331212
</BAIFlex>
12341213
</Form.Item>
12351214

1236-
<Form.Item
1237-
name="resourceGroup"
1238-
label={t('modelStore.ResourceGroup')}
1239-
tooltip={t('modelStore.ResourceGroupTooltip')}
1240-
rules={[{ required: true }]}
1241-
>
1242-
<BAIProjectResourceGroupSelect
1243-
projectName={projectName ?? ''}
1244-
autoSelectDefault
1245-
style={{ width: '100%' }}
1246-
/>
1247-
</Form.Item>
1248-
12491215
<Form.Item
12501216
name="modelFolderId"
12511217
label={t('deployment.ModelFolder')}
@@ -1281,6 +1247,13 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
12811247
commandInterval: 10,
12821248
commandMaxWaitTime: 15,
12831249
environ: [],
1250+
// Resource group is hidden in the UI — pre-fill from the
1251+
// deployment's current revision so new revisions reuse the
1252+
// same RG. If there is no current revision (first-revision
1253+
// flow) the hidden picker inside ResourceAllocationFormItems
1254+
// falls back to its `autoSelectFirstResourceGroup` default.
1255+
resourceGroup:
1256+
currentRevision?.resourceConfig?.resourceGroupName ?? undefined,
12841257
})}
12851258
>
12861259
{currentRevision ? (
@@ -1531,9 +1504,19 @@ const DeploymentAddRevisionModal: React.FC<DeploymentAddRevisionModalProps> = ({
15311504
{t('deployment.step.ClusterAndResources')}
15321505
</SectionHeader>
15331506
<Suspense fallback={<Skeleton active paragraph={{ rows: 4 }} />}>
1507+
{/*
1508+
hideResourceGroupFormItem: the resource group selector is
1509+
hidden from the UI — revisions inherit the deployment's RG and
1510+
`AddRevisionInput.resourceConfig` no longer accepts a resource
1511+
group. The form value is still tracked (prefilled from
1512+
`currentRevision` or auto-selected) so that
1513+
`ResourcePresetSelect` can filter allocatable presets by the
1514+
same RG the deployment runs in.
1515+
*/}
15341516
<ResourceAllocationFormItems
15351517
enableResourcePresets
15361518
autoSelectFirstResourceGroup
1519+
hideResourceGroupFormItem
15371520
/>
15381521
</Suspense>
15391522

0 commit comments

Comments
 (0)