Skip to content

Commit 1503ba8

Browse files
yomybabyhummingbbird
authored andcommitted
Refactor and bugfix related to duplicated name validation
1 parent b775ea5 commit 1503ba8

File tree

4 files changed

+126
-128
lines changed

4 files changed

+126
-128
lines changed

react/src/components/ComputeSessionNodeItems/EditableSessionName.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ const EditableSessionName: React.FC<EditableSessionNameProps> = ({
152152
);
153153
},
154154
onError: (err) => {
155-
if (session.name !== optimisticName) {
155+
// if the session name is not changed, do not show error
156+
if (session.name !== values.sessionName) {
156157
message.error(t('session.FailToRenameSession'));
157158
}
158159
},
@@ -168,9 +169,7 @@ const EditableSessionName: React.FC<EditableSessionNameProps> = ({
168169
<Form.Item
169170
name="sessionName"
170171
rules={validationRules}
171-
style={{
172-
margin: 0,
173-
}}
172+
validateDebounce={200}
174173
>
175174
<Input
176175
size="large"

react/src/components/SessionNameFormItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const SessionNameFormItem: React.FC<SessionNameFormItemProps> = ({
1818
<Form.Item
1919
label={t('session.launcher.SessionName')}
2020
name="sessionName"
21-
validateDebounce={500}
21+
validateDebounce={200}
2222
// Original rule : /^(?=.{4,64}$)\w[\w.-]*\w$/
2323
// https://github.com/lablup/backend.ai/blob/main/src/ai/backend/manager/api/session.py#L355-L356
2424
rules={validationRules}

react/src/hooks/useValidateServiceName.tsx

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useCurrentProjectValue } from './useCurrentProject';
33
import { FormItemProps } from 'antd';
44
import type { RuleObject } from 'antd/es/form';
55
import _ from 'lodash';
6+
import { useMemo } from 'react';
67
import { useTranslation } from 'react-i18next';
78
import { graphql, useRelayEnvironment, fetchQuery } from 'react-relay';
89

@@ -13,46 +14,46 @@ export const useValidateServiceName = (): Exclude<
1314
const { t } = useTranslation();
1415
const relayEvn = useRelayEnvironment();
1516
const currentProject = useCurrentProjectValue();
16-
return [
17-
{
18-
min: 4,
19-
message: t('session.validation.SessionNameTooShort'),
20-
},
21-
{
22-
max: 24,
23-
message: t('modelService.ServiceNameMaxLength'),
24-
type: 'string',
25-
},
26-
{
27-
validator(f: RuleObject, value: string) {
28-
if (_.isEmpty(value)) {
29-
return Promise.resolve();
30-
}
31-
if (!/^\w/.test(value)) {
32-
return Promise.reject(
33-
t('session.validation.SessionNameShouldStartWith'),
34-
);
35-
}
17+
return useMemo(
18+
() => [
19+
{
20+
min: 4,
21+
message: t('session.validation.SessionNameTooShort'),
22+
},
23+
{
24+
max: 24,
25+
message: t('modelService.ServiceNameMaxLength'),
26+
type: 'string',
27+
},
28+
{
29+
validator(f: RuleObject, value: string) {
30+
if (_.isEmpty(value)) {
31+
return Promise.resolve();
32+
}
33+
if (!/^\w/.test(value)) {
34+
return Promise.reject(
35+
t('session.validation.SessionNameShouldStartWith'),
36+
);
37+
}
3638

37-
if (!/\w$/.test(value)) {
38-
return Promise.reject(
39-
t('session.validation.SessionNameShouldEndWith'),
40-
);
41-
}
39+
if (!/\w$/.test(value)) {
40+
return Promise.reject(
41+
t('session.validation.SessionNameShouldEndWith'),
42+
);
43+
}
4244

43-
if (!/^[\w.-]*$/.test(value)) {
44-
return Promise.reject(
45-
t('session.validation.SessionNameInvalidCharacter'),
46-
);
47-
}
48-
return Promise.resolve();
45+
if (!/^[\w.-]*$/.test(value)) {
46+
return Promise.reject(
47+
t('session.validation.SessionNameInvalidCharacter'),
48+
);
49+
}
50+
return Promise.resolve();
51+
},
4952
},
50-
},
51-
{
52-
validator: async (f: RuleObject, value: string) => {
53-
if (!value) return Promise.resolve();
54-
return (
55-
fetchQuery<useValidateServiceNameQuery>(
53+
{
54+
validator: async (f: RuleObject, value: string) => {
55+
if (!value) return Promise.resolve();
56+
return fetchQuery<useValidateServiceNameQuery>(
5657
relayEvn,
5758
graphql`
5859
query useValidateServiceNameQuery(
@@ -79,23 +80,21 @@ export const useValidateServiceName = (): Exclude<
7980
},
8081
)
8182
.toPromise()
82-
.then((data) => {
83-
if ((data?.endpoint_list?.total_count ?? 0) > 0) {
84-
return Promise.reject(
85-
t('session.launcher.SessionAlreadyExists'),
86-
);
87-
}
88-
return Promise.resolve();
89-
})
90-
// reject only when exact duplicate is detected
9183
.catch((err: any) => {
84+
// ignore network error
9285
return Promise.resolve();
9386
})
94-
);
87+
.then((data) => {
88+
if ((data?.endpoint_list?.total_count ?? 0) > 0) {
89+
return Promise.reject(t('modelService.ServiceAlreadyExists'));
90+
}
91+
});
92+
},
93+
},
94+
{
95+
required: true,
9596
},
96-
},
97-
{
98-
required: true,
99-
},
100-
];
97+
],
98+
[relayEvn, currentProject.id, t],
99+
);
101100
};

react/src/hooks/useValidateSessionName.tsx

Lines changed: 71 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useCurrentProjectValue } from './useCurrentProject';
33
import type { RuleObject } from 'antd/es/form';
44
import { FormItemProps } from 'antd/lib';
55
import _ from 'lodash';
6+
import { useMemo } from 'react';
67
import { useTranslation } from 'react-i18next';
78
import { graphql, useRelayEnvironment, fetchQuery } from 'react-relay';
89

@@ -12,80 +13,79 @@ export const useValidateSessionName = (
1213
const { t } = useTranslation();
1314
const relayEvn = useRelayEnvironment();
1415
const currentProject = useCurrentProjectValue();
15-
return [
16-
{
17-
min: 4,
18-
message: t('session.validation.SessionNameTooShort'),
19-
},
20-
{
21-
max: 64,
22-
message: t('session.validation.SessionNameTooLong64'),
23-
},
24-
{
25-
validator(f: RuleObject, value: string) {
26-
if (_.isEmpty(value)) {
27-
return Promise.resolve();
28-
}
29-
if (!/^\w/.test(value)) {
30-
return Promise.reject(
31-
t('session.validation.SessionNameShouldStartWith'),
32-
);
33-
}
34-
35-
if (!/\w$/.test(value)) {
36-
return Promise.reject(
37-
t('session.validation.SessionNameShouldEndWith'),
38-
);
39-
}
40-
41-
if (!/^[\w.-]*$/.test(value)) {
42-
return Promise.reject(
43-
t('session.validation.SessionNameInvalidCharacter'),
44-
);
45-
}
46-
return Promise.resolve();
16+
return useMemo(
17+
() => [
18+
{
19+
min: 4,
20+
message: t('session.validation.SessionNameTooShort'),
4721
},
48-
},
49-
{
50-
validator: async (f: RuleObject, value: string) => {
51-
if (value === currentName) {
52-
return Promise.resolve();
53-
}
54-
return fetchQuery<useValidateSessionNameQuery>(
55-
relayEvn,
56-
graphql`
57-
query useValidateSessionNameQuery(
58-
$projectId: UUID!
59-
$filter: String
60-
) {
61-
compute_session_nodes(project_id: $projectId, filter: $filter) {
62-
count
63-
}
64-
}
65-
`,
66-
{
67-
projectId: currentProject.id,
68-
filter: `status != "TERMINATED" & status != "CANCELLED" & name == "${value}"`,
69-
},
70-
)
71-
.toPromise()
72-
.then((data) => {
73-
if (!data || !data.compute_session_nodes) {
74-
return Promise.reject(
75-
'Unable to validate service name. Please try again.',
76-
);
77-
}
78-
if ((data?.compute_session_nodes?.count ?? 0) > 0) {
79-
return Promise.reject(t('session.launcher.SessionAlreadyExists'));
80-
}
22+
{
23+
max: 64,
24+
message: t('session.validation.SessionNameTooLong64'),
25+
},
26+
{
27+
validator(f: RuleObject, value: string) {
28+
if (_.isEmpty(value)) {
8129
return Promise.resolve();
82-
})
83-
.catch((err: any) => {
30+
}
31+
if (!/^\w/.test(value)) {
32+
return Promise.reject(
33+
t('session.validation.SessionNameShouldStartWith'),
34+
);
35+
}
36+
37+
if (!/\w$/.test(value)) {
8438
return Promise.reject(
85-
err?.message || err || t('error.UnknownError'),
39+
t('session.validation.SessionNameShouldEndWith'),
8640
);
87-
});
41+
}
42+
43+
if (!/^[\w.-]*$/.test(value)) {
44+
return Promise.reject(
45+
t('session.validation.SessionNameInvalidCharacter'),
46+
);
47+
}
48+
return Promise.resolve();
49+
},
50+
},
51+
{
52+
validator: async (f: RuleObject, value: string) => {
53+
if (value === currentName || !value) {
54+
return Promise.resolve();
55+
}
56+
return fetchQuery<useValidateSessionNameQuery>(
57+
relayEvn,
58+
graphql`
59+
query useValidateSessionNameQuery(
60+
$projectId: UUID!
61+
$filter: String
62+
) {
63+
compute_session_nodes(project_id: $projectId, filter: $filter) {
64+
count
65+
}
66+
}
67+
`,
68+
{
69+
projectId: currentProject.id,
70+
filter: `status != "TERMINATED" & status != "CANCELLED" & name == "${value}"`,
71+
},
72+
)
73+
.toPromise()
74+
.catch((err: any) => {
75+
// ignore network error
76+
return;
77+
})
78+
.then((data) => {
79+
if ((data?.compute_session_nodes?.count ?? 0) > 0) {
80+
return Promise.reject(
81+
t('session.launcher.SessionAlreadyExists'),
82+
);
83+
}
84+
});
85+
},
8886
},
89-
},
90-
];
87+
currentName ? { required: true } : {},
88+
],
89+
[currentName, relayEvn, currentProject.id, t],
90+
);
9191
};

0 commit comments

Comments
 (0)