Skip to content

Commit 5bc0be5

Browse files
committed
fix(FR-2684): fix review summary showing all '-' for pre-filled launcher
Two root causes fixed: 1. ReviewSummary had 'use memo' — the React Compiler memoized it on the stable `form` reference, so shouldUpdate re-renders were bailed out even after form.setFieldsValue() set the pre-fill values. Removed 'use memo' so shouldUpdate correctly re-renders the summary. 2. Pre-fill params (resourceGroup, resourcePresetId) were read in DeploymentLauncherCreateView via searchParams and passed as props, which was fragile across Suspense boundaries. Now DeploymentLauncherPageContent reads all pre-fill params directly via nuqs useQueryStates so nuqs tracks and preserves them across step navigation URL updates. preFilledValues prop removed; prop drilling eliminated.
1 parent 8280c1d commit 5bc0be5

31 files changed

Lines changed: 839 additions & 286 deletions

react/src/components/DeploymentLauncherPageContent.tsx

Lines changed: 635 additions & 176 deletions
Large diffs are not rendered by default.

react/src/components/ModelCardDrawer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ const ModelCardDrawer: React.FC<ModelCardDrawerProps> = ({
151151
// Flow 7 (FR-2684): [Deploy | ▼] split button backed by
152152
// useDeploymentLauncher. Primary action fires Quick Deploy via
153153
// createModelDeployment; the dropdown item navigates to the
154-
// full launcher page at /deployments/new?model=<folderId>.
154+
// full launcher page at /deployments/start?model=<folderId>.
155155
<Space.Compact>
156156
<BAIButton
157157
type="primary"

react/src/components/ResourcePresetSelect.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
BAIResourceNumberWithIcon,
2020
} from 'backend.ai-ui';
2121
import * as _ from 'lodash-es';
22-
import React, { useTransition } from 'react';
22+
import React, { useEffect, useTransition } from 'react';
2323
import { useTranslation } from 'react-i18next';
2424
import { graphql, useLazyLoadQuery } from 'react-relay';
2525

@@ -45,12 +45,14 @@ export interface ResourcePresetSelectProps extends Omit<
4545
showMinimumRequired?: boolean;
4646
showCustom?: boolean;
4747
resourceGroup?: string;
48+
autoSelectDefault?: boolean;
4849
}
4950
const ResourcePresetSelect: React.FC<ResourcePresetSelectProps> = ({
5051
allocatablePresetNames,
5152
showCustom,
5253
showMinimumRequired,
5354
resourceGroup,
55+
autoSelectDefault,
5456
...selectProps
5557
}) => {
5658
const [fetchKey, updateFetchKey] = useUpdatableState('first');
@@ -98,6 +100,25 @@ const ResourcePresetSelect: React.FC<ResourcePresetSelectProps> = ({
98100
)
99101
: resource_presets;
100102

103+
const firstAvailablePresetName = [...(resourcePresets ?? [])]
104+
.filter(
105+
(p): p is NonNullable<typeof p> =>
106+
p != null &&
107+
(!allocatablePresetNames ||
108+
allocatablePresetNames.includes(p.name ?? '')),
109+
)
110+
.sort((a, b) => localeCompare(a.name ?? '', b.name ?? ''))[0]?.name;
111+
112+
useEffect(() => {
113+
if (autoSelectDefault && !controllableValue && firstAvailablePresetName) {
114+
setControllableValue(firstAvailablePresetName, {
115+
value: firstAvailablePresetName,
116+
selectedLabel: firstAvailablePresetName,
117+
});
118+
}
119+
// eslint-disable-next-line react-hooks/exhaustive-deps
120+
}, [autoSelectDefault]);
121+
101122
return (
102123
<Select
103124
loading={isPendingUpdate}
@@ -151,6 +172,7 @@ const ResourcePresetSelect: React.FC<ResourcePresetSelectProps> = ({
151172
: undefined;
152173
return {
153174
value: preset?.name,
175+
selectedLabel: preset?.name,
154176
label: (
155177
<BAIFlex direction="row" justify="between" gap={'xs'}>
156178
{preset?.name}
@@ -206,11 +228,7 @@ const ResourcePresetSelect: React.FC<ResourcePresetSelectProps> = ({
206228
{...selectProps}
207229
value={controllableValue}
208230
onChange={setControllableValue}
209-
optionLabelProp={
210-
_.includes(['custom', 'minimum-required'], controllableValue)
211-
? 'selectedLabel'
212-
: 'label'
213-
}
231+
optionLabelProp="selectedLabel"
214232
onOpenChange={(open) => {
215233
selectProps.onOpenChange && selectProps.onOpenChange(open);
216234
if (open) {

react/src/components/SessionLauncherErrorTourProps.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const SessionLauncherValidationTour: React.FC<
3030
target: () =>
3131
(
3232
document.getElementsByClassName('bai-card-error')?.[0] as HTMLElement
33-
)?.querySelector('.ant-card-extra') as HTMLElement,
33+
)?.querySelector('.ant-card-head') as HTMLElement,
3434
},
3535
{
3636
title: t('tourGuide.neoSessionLauncher.ValidationErrorTitle'),

react/src/components/VFolderDeployModal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ const VFolderDeployModalContent: React.FC<VFolderDeployModalContentProps> = ({
380380
{supportsQuickDeploy && vfolderId ? (
381381
// Flow 7 (FR-2684): [Deploy | ▼] split button. Primary fires
382382
// deployVfolderV2 with selected preset/resource group; the dropdown
383-
// item navigates to the full launcher at /deployments/new?model=<id>.
383+
// item navigates to the full launcher at /deployments/start?model=<id>.
384384
<Space.Compact>
385385
<BAIButton
386386
type="primary"
@@ -407,7 +407,10 @@ const VFolderDeployModalContent: React.FC<VFolderDeployModalContentProps> = ({
407407
openLauncher({
408408
modelFolderId: vfolderId,
409409
resourceGroup: effectiveResourceGroup,
410-
resourcePreset: effectivePresetId,
410+
// Pass name so ResourcePresetSelect can match it
411+
resourcePreset: availablePresets.find(
412+
(p) => toLocalId(p.id) === effectivePresetId,
413+
)?.name,
411414
});
412415
},
413416
},

react/src/components/VFolderLazyView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ const VFolderLazyView: React.FC<VFolderLazyViewProps> = ({
4444
{clickable ? (
4545
<Typography.Link
4646
onClick={() => {
47+
const searchParams = new URLSearchParams(location.search);
48+
searchParams.set('folder', toLocalId(vfolder_node.id));
4749
webuiNavigate({
4850
pathname: location.pathname,
49-
search: new URLSearchParams({
50-
folder: toLocalId(vfolder_node.id),
51-
}).toString(),
51+
search: searchParams.toString(),
5252
});
5353
}}
5454
>

react/src/hooks/useBAISetting.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { CustomThemeConfig } from 'src/helper/customThemeConfig';
1414

1515
export interface UserSettings {
1616
has_opened_tour_neo_session_validation?: boolean;
17+
has_opened_tour_neo_deployment_validation?: boolean;
1718
desktop_notification?: boolean;
1819
compact_sidebar?: boolean;
1920
preserve_login?: boolean;

react/src/hooks/useDeploymentLauncher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export interface DeployInstantlyResult {
4242
* sensible defaults (replicas=1, openToPublic=false, current project +
4343
* domain, current resource group as a fallback). Returns the new
4444
* deployment id and raises a BAI notification on success/failure.
45-
* - `openLauncher`: navigates to `/deployments/new?model=<folderId>` so the
45+
* - `openLauncher`: navigates to `/deployments/start?model=<folderId>` so the
4646
* user can configure a deployment in the full launcher UI.
4747
*
4848
* The hook does not wrap the legacy REST-based `useModelServiceLauncher` —
@@ -236,7 +236,7 @@ export const useDeploymentLauncher = (): {
236236
if (input.resourceGroup && input.resourcePreset) {
237237
params.set('step', 'review');
238238
}
239-
navigate(`/deployments/new?${params.toString()}`);
239+
navigate(`/deployments/start?${params.toString()}`);
240240
};
241241

242242
return {

react/src/pages/DeploymentLauncherPage.tsx

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,16 @@ import {
1919
useCurrentResourceGroupValue,
2020
} from '../hooks/useCurrentProject';
2121
import { App, Form, Skeleton, Typography, theme } from 'antd';
22-
import {
23-
BAIButton,
24-
BAIFlex,
25-
toGlobalId,
26-
toLocalId,
27-
useBAILogger,
28-
} from 'backend.ai-ui';
22+
import { BAIFlex, toGlobalId, toLocalId, useBAILogger } from 'backend.ai-ui';
2923
import React, { Suspense } from 'react';
3024
import { useTranslation } from 'react-i18next';
3125
import { graphql, useLazyLoadQuery, useMutation } from 'react-relay';
32-
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
26+
import { useNavigate, useParams } from 'react-router-dom';
3327

3428
/**
3529
* DeploymentLauncherPage — page-level orchestrator for the deployment launcher.
3630
*
37-
* Handles both create (`/deployments/new`) and edit
31+
* Handles both create (`/deployments/start`) and edit
3832
* (`/deployments/:deploymentId/edit`) routes. Owns:
3933
* - Relay `useLazyLoadQuery` for the edit-mode deployment snapshot.
4034
* - The antd `FormInstance` that the content component binds to.
@@ -63,31 +57,16 @@ const DeploymentLauncherPage: React.FC = () => {
6357
};
6458

6559
/**
66-
* Create-mode view — no deployment fragment to pre-fill from. Reads URL
67-
* params forwarded by entry points (model store split button, VFolderDeployModal)
68-
* and builds a preFilledValues object passed down to the content component.
69-
* Supported params: `model`, `resourceGroup`, `resourcePresetId`.
60+
* Create-mode view — no deployment fragment to pre-fill from. Pre-fill
61+
* params (model, resourceGroup, resourcePresetId, step) are read directly
62+
* from the URL inside DeploymentLauncherPageContent via nuqs so they are
63+
* always in sync and never stripped by step navigation URL updates.
7064
*/
7165
const DeploymentLauncherCreateView: React.FC = () => {
7266
'use memo';
73-
const [searchParams] = useSearchParams();
7467
const [form] = Form.useForm<DeploymentLauncherFormValue>();
7568

76-
const preFilledValues: Partial<DeploymentLauncherFormValue> = {};
77-
const model = searchParams.get('model');
78-
const resourceGroup = searchParams.get('resourceGroup');
79-
const resourcePresetId = searchParams.get('resourcePresetId');
80-
if (model) preFilledValues.modelFolderId = model;
81-
if (resourceGroup) preFilledValues.resourceGroup = resourceGroup;
82-
if (resourcePresetId) preFilledValues.resourcePresetId = resourcePresetId;
83-
84-
return (
85-
<DeploymentLauncherPageLayout
86-
mode="create"
87-
form={form}
88-
preFilledValues={preFilledValues}
89-
/>
90-
);
69+
return <DeploymentLauncherPageLayout mode="create" form={form} />;
9170
};
9271

9372
/**
@@ -139,7 +118,6 @@ interface DeploymentLauncherPageLayoutProps {
139118
deploymentFrgmt?: React.ComponentProps<
140119
typeof DeploymentLauncherPageContent
141120
>['deploymentFrgmt'];
142-
preFilledValues?: Partial<DeploymentLauncherFormValue>;
143121
}
144122

145123
/**
@@ -149,7 +127,7 @@ interface DeploymentLauncherPageLayoutProps {
149127
*/
150128
const DeploymentLauncherPageLayout: React.FC<
151129
DeploymentLauncherPageLayoutProps
152-
> = ({ mode, form, deploymentId, deploymentFrgmt, preFilledValues }) => {
130+
> = ({ mode, form, deploymentId, deploymentFrgmt }) => {
153131
'use memo';
154132

155133
const { t } = useTranslation();
@@ -396,25 +374,11 @@ const DeploymentLauncherPageLayout: React.FC<
396374
mode={mode}
397375
form={form}
398376
deploymentFrgmt={deploymentFrgmt}
399-
preFilledValues={preFilledValues}
400377
onValuesChange={() => setIsDirty(true)}
378+
onCancel={handleCancel}
379+
onSubmit={handleSubmit}
380+
isSubmitting={isSubmitting}
401381
/>
402-
403-
<BAIFlex
404-
direction="row"
405-
justify="end"
406-
gap="sm"
407-
style={{ marginTop: token.marginLG }}
408-
>
409-
<BAIButton onClick={handleCancel} disabled={isSubmitting}>
410-
{t('button.Cancel')}
411-
</BAIButton>
412-
<BAIButton type="primary" loading={isSubmitting} action={handleSubmit}>
413-
{mode === 'edit'
414-
? t('deployment.UpdateDeployment')
415-
: t('deployment.CreateDeployment')}
416-
</BAIButton>
417-
</BAIFlex>
418382
</BAIFlex>
419383
);
420384
};

react/src/routes.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const FileUploadManager = React.lazy(
6767
);
6868
// FR-2675 — The legacy `ServiceLauncherCreatePage` / `ServiceLauncherUpdatePage`
6969
// routes have been redirected to the new Deployment launcher below so the
70-
// new `/deployments/new` + `/deployments/:deploymentId/edit` paths are the
70+
// new `/deployments/start` + `/deployments/:deploymentId/edit` paths are the
7171
// sole launcher entry points. The underlying `ServiceLauncherPageContent`
7272
// component is still imported transitively (by `useModelServiceLauncher`,
7373
// `LegacyModelTryContentButton`, etc.) and is scheduled for removal in a
@@ -301,7 +301,7 @@ export const mainLayoutChildRoutes: RouteObject[] = [
301301
},
302302
},
303303
{
304-
path: 'new',
304+
path: 'start',
305305
handle: { labelKey: 'modelService.StartNewService' },
306306
element: (
307307
<Suspense
@@ -380,12 +380,15 @@ export const mainLayoutChildRoutes: RouteObject[] = [
380380
element: <WebUINavigate to="/deployments" replace />,
381381
},
382382
{
383-
// FR-2675 — Legacy `/service/start` → new `/deployments/new`.
383+
// FR-2675 — Legacy `/service/start` → new `/deployments/start`.
384384
path: 'start',
385385
Component: () => {
386386
const location = useLocation();
387387
return (
388-
<WebUINavigate to={'/deployments/new' + location.search} replace />
388+
<WebUINavigate
389+
to={'/deployments/start' + location.search}
390+
replace
391+
/>
389392
);
390393
},
391394
},

0 commit comments

Comments
 (0)