Skip to content

Commit 6dec62a

Browse files
committed
Was added create fleet wizard
1 parent 965359a commit 6dec62a

File tree

14 files changed

+301
-12
lines changed

14 files changed

+301
-12
lines changed

frontend/src/locale/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"runs": "Runs",
7676
"models": "Models",
7777
"fleets": "Fleets",
78+
"fleet": "Fleet",
7879
"project": "project",
7980
"project_other": "Projects",
8081
"general": "General",

frontend/src/pages/Fleets/Details/components/FleetFormFields/constants.tsx renamed to frontend/src/pages/Fleets/Add/FleetFormFields/constants.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,4 @@ export const getMaxInstancesValidator = (minInstancesFieldPath: string) =>
112112
return value >= minInstances;
113113
});
114114

115-
export const idleDurationValidator = yup.string().matches(/^[1-9]\d*[smhdw]$/, 'Invalid duration');
115+
export const idleDurationValidator = yup.string().matches(/^\d+[smhdw]$/, 'Invalid duration');

frontend/src/pages/Fleets/Details/components/FleetFormFields/index.tsx renamed to frontend/src/pages/Fleets/Add/FleetFormFields/index.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export function FleetFormFields<T extends FieldValues = FieldValues>({
1818
const { t } = useTranslation();
1919
const [openHelpPanel] = useHelpPanel();
2020

21+
const getFieldNameWitPrefix = (name: string) => {
22+
if (!fieldNamePrefix) {
23+
return name;
24+
}
25+
26+
[fieldNamePrefix, name].join('.');
27+
};
28+
2129
return (
2230
<SpaceBetween direction="vertical" size="l">
2331
<FormInput
@@ -27,7 +35,7 @@ export function FleetFormFields<T extends FieldValues = FieldValues>({
2735
control={control}
2836
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
2937
// @ts-expect-error
30-
name={`${fieldNamePrefix}.name`}
38+
name={getFieldNameWitPrefix(`name`)}
3139
disabled={disabledAllFields}
3240
/>
3341

@@ -38,7 +46,7 @@ export function FleetFormFields<T extends FieldValues = FieldValues>({
3846
control={control}
3947
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
4048
// @ts-expect-error
41-
name={`${fieldNamePrefix}.min_instances`}
49+
name={getFieldNameWitPrefix(`min_instances`)}
4250
disabled={disabledAllFields}
4351
type="number"
4452
/>
@@ -51,7 +59,7 @@ export function FleetFormFields<T extends FieldValues = FieldValues>({
5159
control={control}
5260
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
5361
// @ts-expect-error
54-
name={`${fieldNamePrefix}.max_instances`}
62+
name={getFieldNameWitPrefix(`max_instances`)}
5563
disabled={disabledAllFields}
5664
type="number"
5765
/>
@@ -63,7 +71,7 @@ export function FleetFormFields<T extends FieldValues = FieldValues>({
6371
control={control}
6472
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
6573
// @ts-expect-error
66-
name={`${fieldNamePrefix}.idle_duration`}
74+
name={getFieldNameWitPrefix(`idle_duration`)}
6775
disabled={disabledAllFields}
6876
/>
6977
</SpaceBetween>

frontend/src/pages/Fleets/Details/components/FleetFormFields/type.ts renamed to frontend/src/pages/Fleets/Add/FleetFormFields/type.ts

File renamed without changes.
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
import React, { useState } from 'react';
2+
import { useForm } from 'react-hook-form';
3+
import { useTranslation } from 'react-i18next';
4+
import { useNavigate } from 'react-router-dom';
5+
import { isNil } from 'lodash';
6+
import * as yup from 'yup';
7+
import { WizardProps } from '@cloudscape-design/components';
8+
9+
import { Container, FormSelect, KeyValuePairs, SpaceBetween, Wizard } from 'components';
10+
11+
import { useBreadcrumbs, useConfirmationDialog, useNotifications } from 'hooks';
12+
import { useProjectFilter } from 'hooks/useProjectFilter';
13+
import { ROUTES } from 'routes';
14+
import { useApplyFleetMutation } from 'services/fleet';
15+
16+
import { useYupValidationResolver } from 'pages/Project/hooks/useYupValidationResolver';
17+
18+
import { getMaxInstancesValidator, getMinInstancesValidator, idleDurationValidator } from './FleetFormFields/constants';
19+
import { FleetFormFields } from './FleetFormFields';
20+
21+
import { IFleetWizardForm } from './types';
22+
23+
const requiredFieldError = 'This is required field';
24+
const namesFieldError = 'Only latin characters, dashes, underscores, and digits';
25+
26+
const fleetStepIndex = 1;
27+
28+
const fleetValidationSchema = yup.object({
29+
project_name: yup
30+
.string()
31+
.required(requiredFieldError)
32+
.matches(/^[a-zA-Z0-9-_]+$/, namesFieldError),
33+
min_instances: getMinInstancesValidator('max_instances'),
34+
max_instances: getMaxInstancesValidator('min_instances'),
35+
idle_duration: idleDurationValidator,
36+
});
37+
38+
export const FleetAdd: React.FC = () => {
39+
const { t } = useTranslation();
40+
const navigate = useNavigate();
41+
const [pushNotification] = useNotifications();
42+
const [openConfirmationDialog] = useConfirmationDialog();
43+
const [applyFleet, { isLoading: isApplyingFleet }] = useApplyFleetMutation();
44+
const [activeStepIndex, setActiveStepIndex] = useState(0);
45+
const resolver = useYupValidationResolver(fleetValidationSchema);
46+
const { projectOptions } = useProjectFilter({ localStorePrefix: 'add-fleet-page' });
47+
48+
const loading = isApplyingFleet;
49+
50+
const formMethods = useForm<IFleetWizardForm>({
51+
resolver,
52+
defaultValues: {
53+
min_instances: 0,
54+
idle_duration: '5m',
55+
},
56+
});
57+
58+
const { handleSubmit, control, clearErrors, trigger, watch, getValues } = formMethods;
59+
const formValues = watch();
60+
61+
const getFormValuesForFleetApplying = (): IApplyFleetPlanRequestRequest => {
62+
const { min_instances, max_instances, idle_duration, name } = getValues();
63+
64+
return {
65+
plan: {
66+
spec: {
67+
configuration: {
68+
...(name ? { name } : {}),
69+
nodes: {
70+
min: min_instances,
71+
...(max_instances ? { max: max_instances } : {}),
72+
},
73+
...(idle_duration ? { idle_duration } : {}),
74+
},
75+
profile: {},
76+
},
77+
},
78+
force: false,
79+
};
80+
};
81+
82+
useBreadcrumbs([
83+
{
84+
text: t('navigation.fleets'),
85+
href: ROUTES.FLEETS.LIST,
86+
},
87+
88+
{
89+
text: t('common.create_wit_text', { text: t('navigation.fleet') }),
90+
href: ROUTES.FLEETS.ADD,
91+
},
92+
]);
93+
94+
const validateName = async () => {
95+
return await trigger(['project_name']);
96+
};
97+
98+
const validateFleet = async () => {
99+
return await trigger(['min_instances', 'max_instances', 'idle_duration']);
100+
};
101+
102+
const emptyValidator = async () => Promise.resolve(true);
103+
104+
const onNavigate = ({
105+
requestedStepIndex,
106+
reason,
107+
}: {
108+
requestedStepIndex: number;
109+
reason: WizardProps.NavigationReason;
110+
}) => {
111+
const stepValidators = [validateName, validateFleet, emptyValidator];
112+
113+
if (reason === 'next') {
114+
stepValidators[activeStepIndex]?.().then((isValid) => {
115+
if (isValid) {
116+
if (activeStepIndex === fleetStepIndex && formValues?.['min_instances'] > 0) {
117+
openConfirmationDialog({
118+
title: 'Are sure want to set min instances above than 0?',
119+
content: null,
120+
onConfirm: () => setActiveStepIndex(requestedStepIndex),
121+
});
122+
} else {
123+
setActiveStepIndex(requestedStepIndex);
124+
}
125+
}
126+
});
127+
} else {
128+
setActiveStepIndex(requestedStepIndex);
129+
}
130+
};
131+
132+
const onNavigateHandler: WizardProps['onNavigate'] = ({ detail: { requestedStepIndex, reason } }) => {
133+
onNavigate({ requestedStepIndex, reason });
134+
};
135+
136+
const onCancelHandler = () => {
137+
navigate(ROUTES.FLEETS.LIST);
138+
};
139+
140+
const onSubmitWizard = async () => {
141+
const isValid = await trigger();
142+
143+
const { project_name } = getValues();
144+
145+
if (!isValid) {
146+
return;
147+
}
148+
149+
clearErrors();
150+
151+
const request = applyFleet({
152+
projectName: project_name,
153+
...getFormValuesForFleetApplying(),
154+
}).unwrap();
155+
156+
request
157+
.then(async (data) => {
158+
pushNotification({
159+
type: 'success',
160+
content: t('fleets.create.success_notification'),
161+
});
162+
163+
navigate(ROUTES.FLEETS.DETAILS.FORMAT(data.project_name, data.id));
164+
})
165+
.catch((error) => {
166+
pushNotification({
167+
type: 'error',
168+
content: t('common.server_error', { error: error?.error ?? error }),
169+
});
170+
});
171+
};
172+
173+
const onSubmit = () => {
174+
if (activeStepIndex < 2) {
175+
onNavigate({ requestedStepIndex: activeStepIndex + 1, reason: 'next' });
176+
} else {
177+
onSubmitWizard().catch(console.log);
178+
}
179+
};
180+
181+
const getDefaultFleetSummary = () => {
182+
const summaryFields: Array<keyof IFleetWizardForm> = ['name', 'min_instances', 'max_instances', 'idle_duration'];
183+
184+
const result: string[] = [];
185+
186+
summaryFields.forEach((fieldName) => {
187+
if (!isNil(formValues?.[fieldName])) {
188+
result.push(`${t(`fleets.edit.${fieldName}`)}: ${formValues?.[fieldName]}`);
189+
}
190+
});
191+
192+
return result.join(', ');
193+
};
194+
195+
return (
196+
<form onSubmit={handleSubmit(onSubmit)}>
197+
<Wizard
198+
activeStepIndex={activeStepIndex}
199+
onNavigate={onNavigateHandler}
200+
onSubmit={onSubmitWizard}
201+
i18nStrings={{
202+
stepNumberLabel: (stepNumber) => `Step ${stepNumber}`,
203+
navigationAriaLabel: 'Steps',
204+
cancelButton: t('common.cancel'),
205+
previousButton: t('common.previous'),
206+
nextButton: t('common.next'),
207+
optional: 'optional',
208+
}}
209+
onCancel={onCancelHandler}
210+
submitButtonText={t('projects.wizard.submit')}
211+
steps={[
212+
{
213+
title: 'Project',
214+
content: (
215+
<Container>
216+
<SpaceBetween direction="vertical" size="l">
217+
<FormSelect
218+
label={t('projects.edit.project_name')}
219+
control={control}
220+
name="project_name"
221+
options={projectOptions}
222+
disabled={loading}
223+
/>
224+
</SpaceBetween>
225+
</Container>
226+
),
227+
},
228+
{
229+
title: 'Fleets',
230+
content: (
231+
<Container>
232+
<SpaceBetween direction="vertical" size="l">
233+
<FleetFormFields<IFleetWizardForm> control={control} disabledAllFields={loading} />
234+
</SpaceBetween>
235+
</Container>
236+
),
237+
},
238+
{
239+
title: 'Summary',
240+
content: (
241+
<Container>
242+
<KeyValuePairs
243+
items={[
244+
{
245+
label: t('projects.edit.project_name'),
246+
value: formValues['project_name'],
247+
},
248+
{
249+
label: 'Default fleet',
250+
value: getDefaultFleetSummary(),
251+
},
252+
]}
253+
/>
254+
</Container>
255+
),
256+
},
257+
]}
258+
/>
259+
</form>
260+
);
261+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { FleetFormFields } from './FleetFormFields/type';
2+
3+
export interface IFleetWizardForm extends FleetFormFields {
4+
project_name: IProject['project_name'];
5+
}

frontend/src/pages/Fleets/List/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import { useTranslation } from 'react-i18next';
3+
import { useNavigate } from 'react-router-dom';
34

45
import { Button, Header, Loader, PropertyFilter, SpaceBetween, Table, Toggle } from 'components';
56

@@ -18,6 +19,7 @@ import styles from './styles.module.scss';
1819

1920
export const FleetList: React.FC = () => {
2021
const { t } = useTranslation();
22+
const navigate = useNavigate();
2123

2224
useBreadcrumbs([
2325
{
@@ -66,6 +68,10 @@ export const FleetList: React.FC = () => {
6668

6769
const isDisabledDeleteButton = !selectedItems?.length || isDeleting;
6870

71+
const addFleetHandler = () => {
72+
navigate(ROUTES.FLEETS.ADD);
73+
};
74+
6975
const deleteClickHandle = () => {
7076
if (!selectedItems?.length) return;
7177

@@ -101,6 +107,8 @@ export const FleetList: React.FC = () => {
101107
{t('common.delete')}
102108
</Button>
103109

110+
<Button onClick={addFleetHandler}>{t('common.create')}</Button>
111+
104112
<Button
105113
iconName="refresh"
106114
disabled={isLoading}

frontend/src/pages/Fleets/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { FleetList } from './List';
22
export { FleetDetails } from './Details';
3+
export { FleetAdd } from './Add';

frontend/src/pages/Project/Add/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import { ROUTES } from 'routes';
1414
import { useApplyFleetMutation } from 'services/fleet';
1515
import { useCreateProjectMutation } from 'services/project';
1616

17-
import { FleetFormFields } from 'pages/Fleets/Details/components/FleetFormFields';
17+
import { FleetFormFields } from 'pages/Fleets/Add/FleetFormFields';
1818
import {
1919
getMaxInstancesValidator,
2020
getMinInstancesValidator,
2121
idleDurationValidator,
22-
} from 'pages/Fleets/Details/components/FleetFormFields/constants';
22+
} from 'pages/Fleets/Add/FleetFormFields/constants';
2323

2424
import { DEFAULT_FLEET_INFO } from '../constants';
2525
import { useYupValidationResolver } from '../hooks/useYupValidationResolver';

0 commit comments

Comments
 (0)