Skip to content

Commit dec2fe2

Browse files
committed
feat(FR-1120): add duplicate check logic and modify validation message to service and session name input
1 parent 03a4fc0 commit dec2fe2

23 files changed

+228
-2045
lines changed

react/src/components/ServiceLauncherPageContent.tsx

Lines changed: 96 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ServiceLauncherPageContentDuplicatedQuery } from '../__generated__/ServiceLauncherPageContentDuplicatedQuery.graphql';
12
import { ServiceLauncherPageContentFragment$key } from '../__generated__/ServiceLauncherPageContentFragment.graphql';
23
import { ServiceLauncherPageContentModifyMutation } from '../__generated__/ServiceLauncherPageContentModifyMutation.graphql';
34
import { ServiceLauncherPageContent_UserInfoQuery } from '../__generated__/ServiceLauncherPageContent_UserInfoQuery.graphql';
@@ -15,7 +16,10 @@ import {
1516
} from '../hooks';
1617
import { KnownAcceleratorResourceSlotName } from '../hooks/backendai';
1718
import { useSuspenseTanQuery, useTanMutation } from '../hooks/reactQueryAlias';
18-
import { useCurrentResourceGroupState } from '../hooks/useCurrentProject';
19+
import {
20+
useCurrentResourceGroupState,
21+
useCurrentProjectValue,
22+
} from '../hooks/useCurrentProject';
1923
import BAIModal, { DEFAULT_BAI_MODAL_Z_INDEX } from './BAIModal';
2024
import EnvVarFormList, { EnvVarFormListValue } from './EnvVarFormList';
2125
import Flex from './Flex';
@@ -46,6 +50,8 @@ import {
4650
Tooltip,
4751
Tag,
4852
} from 'antd';
53+
import { FormItemProps } from 'antd/lib';
54+
import { TFunction } from 'i18next';
4955
import _ from 'lodash';
5056
import React, { Suspense, useState } from 'react';
5157
import { useTranslation } from 'react-i18next';
@@ -54,6 +60,8 @@ import {
5460
useFragment,
5561
useLazyLoadQuery,
5662
useMutation,
63+
fetchQuery,
64+
useRelayEnvironment,
5765
} from 'react-relay';
5866
import { StringParam, useQueryParams } from 'use-query-params';
5967

@@ -129,6 +137,44 @@ interface ServiceLauncherPageContentProps {
129137
endpointFrgmt?: ServiceLauncherPageContentFragment$key | null;
130138
}
131139

140+
export const getServiceNameRules = (
141+
t: TFunction,
142+
): Exclude<FormItemProps['rules'], undefined> => [
143+
{
144+
min: 4,
145+
message: t('modelService.ServiceNameMinLength'),
146+
type: 'string',
147+
},
148+
{
149+
max: 24,
150+
message: t('modelService.ServiceNameMaxLength'),
151+
type: 'string',
152+
},
153+
{
154+
validator(f, value) {
155+
if (_.isEmpty(value)) {
156+
return Promise.resolve();
157+
}
158+
if (!/^\w/.test(value)) {
159+
return Promise.reject(t('modelService.ServiceNameShouldStartWith'));
160+
}
161+
162+
if (!/\w$/.test(value)) {
163+
return Promise.reject(t('modelService.ServiceNameShouldEndWith'));
164+
}
165+
166+
if (!/^[\w-]+$/.test(value)) {
167+
return Promise.reject(t('modelService.ServiceNameInvalidCharacter'));
168+
}
169+
170+
return Promise.resolve();
171+
},
172+
},
173+
{
174+
required: true,
175+
},
176+
];
177+
132178
const ServiceLauncherPageContent: React.FC<ServiceLauncherPageContentProps> = ({
133179
endpointFrgmt = null,
134180
}) => {
@@ -137,6 +183,8 @@ const ServiceLauncherPageContent: React.FC<ServiceLauncherPageContentProps> = ({
137183

138184
const { t } = useTranslation();
139185

186+
const currentProject = useCurrentProjectValue();
187+
140188
const [{ model }] = useQueryParams({
141189
model: StringParam,
142190
});
@@ -637,6 +685,7 @@ const ServiceLauncherPageContent: React.FC<ServiceLauncherPageContentProps> = ({
637685

638686
const [validateServiceData, setValidateServiceData] = useState<any>();
639687

688+
const relayEvn = useRelayEnvironment();
640689
const getAIAcceleratorWithStringifiedKey = (resourceSlot: any) => {
641690
if (Object.keys(resourceSlot).length <= 0) {
642691
return undefined;
@@ -751,29 +800,54 @@ const ServiceLauncherPageContent: React.FC<ServiceLauncherPageContentProps> = ({
751800
<Form.Item
752801
label={t('modelService.ServiceName')}
753802
name="serviceName"
803+
validateDebounce={500}
754804
rules={[
805+
...getServiceNameRules(t),
755806
{
756-
min: 4,
757-
message: t('modelService.ServiceNameMinLength'),
758-
type: 'string',
759-
},
760-
{
761-
max: 24,
762-
message: t('modelService.ServiceNameMaxLength'),
763-
type: 'string',
764-
},
765-
{
766-
pattern: /^(?:[^-]|[^-].*[^-])$/,
767-
message: t(
768-
'modelService.ServiceNameCannotStartWithHyphen',
769-
),
770-
},
771-
{
772-
pattern: /^[\w-]+$/,
773-
message: t('modelService.ServiceNameRule'),
774-
},
775-
{
776-
required: true,
807+
validator: async (_, value) => {
808+
if (!value) return Promise.resolve();
809+
const hasSameName =
810+
await fetchQuery<ServiceLauncherPageContentDuplicatedQuery>(
811+
relayEvn,
812+
graphql`
813+
query ServiceLauncherPageContentDuplicatedQuery(
814+
$projectID: UUID!
815+
$filter: String
816+
$offset: Int!
817+
$limit: Int!
818+
) {
819+
endpoint_list(
820+
project: $projectID
821+
filter: $filter
822+
offset: $offset
823+
limit: $limit
824+
) {
825+
total_count
826+
}
827+
}
828+
`,
829+
{
830+
projectID: currentProject.id,
831+
filter: `name == "${value}"`,
832+
offset: 0,
833+
limit: 1,
834+
},
835+
)
836+
.toPromise()
837+
.then(
838+
(data) =>
839+
(data?.endpoint_list?.total_count ?? 0) >
840+
0,
841+
)
842+
.catch(() => {
843+
return false;
844+
});
845+
return hasSameName
846+
? Promise.reject(
847+
t('modelService.ServiceAlreadyExists'),
848+
)
849+
: Promise.resolve();
850+
},
777851
},
778852
]}
779853
>

react/src/components/SessionNameFormItem.tsx

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { SessionNameFormItemDuplicatedCheckQuery } from '../__generated__/SessionNameFormItemDuplicatedCheckQuery.graphql';
2+
import { useCurrentProjectValue } from '../hooks/useCurrentProject';
13
import { Form, FormItemProps, Input } from 'antd';
24
import { TFunction } from 'i18next';
35
import _ from 'lodash';
46
import React from 'react';
57
import { useTranslation } from 'react-i18next';
8+
import { graphql, fetchQuery, useRelayEnvironment } from 'react-relay';
69

710
interface SessionNameFormItemProps extends FormItemProps {}
811

@@ -32,15 +35,15 @@ export const getSessionNameRules = (
3235
);
3336
}
3437

38+
if (!/\w$/.test(value)) {
39+
return Promise.reject(t('session.validation.SessionNameShouldEndWith'));
40+
}
41+
3542
if (!/^[\w.-]*$/.test(value)) {
3643
return Promise.reject(
3744
t('session.validation.SessionNameInvalidCharacter'),
3845
);
3946
}
40-
41-
if (!/\w$/.test(value) && value.length >= 4) {
42-
return Promise.reject(t('session.validation.SessionNameShouldEndWith'));
43-
}
4447
return Promise.resolve();
4548
},
4649
},
@@ -49,15 +52,54 @@ export const getSessionNameRules = (
4952
const SessionNameFormItem: React.FC<SessionNameFormItemProps> = ({
5053
...formItemProps
5154
}) => {
52-
/* TODO: check SessionNameAlreadyExist */
5355
const { t } = useTranslation();
56+
const relayEvn = useRelayEnvironment();
57+
const currentProject = useCurrentProjectValue();
5458
return (
5559
<Form.Item
5660
label={t('session.launcher.SessionName')}
5761
name="sessionName"
62+
validateDebounce={500}
5863
// Original rule : /^(?=.{4,64}$)\w[\w.-]*\w$/
5964
// https://github.com/lablup/backend.ai/blob/main/src/ai/backend/manager/api/session.py#L355-L356
60-
rules={getSessionNameRules(t)}
65+
rules={[
66+
...getSessionNameRules(t),
67+
{
68+
validator: async (_, value) => {
69+
const hasSameName =
70+
await fetchQuery<SessionNameFormItemDuplicatedCheckQuery>(
71+
relayEvn,
72+
graphql`
73+
query SessionNameFormItemDuplicatedCheckQuery(
74+
$projectId: UUID!
75+
$filter: String
76+
) {
77+
compute_session_nodes(
78+
project_id: $projectId
79+
filter: $filter
80+
) {
81+
count
82+
}
83+
}
84+
`,
85+
{
86+
projectId: currentProject.id,
87+
filter: `status != "TERMINATED" & status != "CANCELLED" & name == "${value}"`,
88+
},
89+
)
90+
.toPromise()
91+
.then((data) => {
92+
return data?.compute_session_nodes?.count !== 0;
93+
})
94+
.catch(() => {
95+
return false;
96+
});
97+
return hasSameName
98+
? Promise.reject(t('session.launcher.SessionAlreadyExists'))
99+
: Promise.resolve();
100+
},
101+
},
102+
]}
61103
{...formItemProps}
62104
>
63105
<Input allowClear autoComplete="off" />

resources/i18n/de.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,15 +938,18 @@
938938
"RoutingsCount": "Routings zählen",
939939
"RuntimeVariant": "Inferenz Laufzeitvariante",
940940
"SeeContainerLogs": "Siehe Containerprotokolle",
941+
"ServiceAlreadyExists": "Servicename existiert bereits",
941942
"ServiceCreated": "Der Modelldienst {{ name }} wurde erfolgreich erstellt.",
942943
"ServiceDelegatedFrom": "Der Modelldienst wird von {{ createdUser }} erstellt, aber das Eigentum an der Sitzung wird an {{ sessionOwner }} delegiert.",
943944
"ServiceEndpoint": "Dienst-Endpunkt",
944945
"ServiceInfo": "Service-Informationen",
945946
"ServiceName": "Dienst Name",
946947
"ServiceNameCannotStartWithHyphen": "Bindestriche (-) dürfen an beiden Enden nicht verwendet werden.",
948+
"ServiceNameInvalidCharacter": "In der Mitte des Dienstnamens kann nur das Alphabet, die Nummer, die '_', '.'-'-' nur eingeschlossen werden.",
947949
"ServiceNameMaxLength": "Bitte geben Sie maximal 24 Zeichen ein.",
948950
"ServiceNameMinLength": "Bitte geben Sie mindestens 4 Zeichen ein.",
949-
"ServiceNameRule": "Erlauben Sie nur alphanumerische Zeichen, Unterstriche (_) und Bindestriche (-) und müssen mit einem alphanumerischen Zeichen enden.",
951+
"ServiceNameShouldEndWith": "Der Sitzungsname muss mit Alphabet, großen und kleinen Zeichen, Zahlen und '_' enden.",
952+
"ServiceNameShouldStartWith": "Der Sitzungsname sollte mit alphabetischen und kleinen Zeichen, Zahlen oder '_' beginnen.",
950953
"ServiceTerminated": "Für den Modelldienst „{{ name }}“ wurde eine Anforderung zum Herunterfahren gesendet.",
951954
"ServiceUpdated": "Der Modelldienst {{ name }} wurde erfolgreich aktualisiert.",
952955
"Services": "Dienstleistungen",

resources/i18n/el.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,15 +934,18 @@
934934
"RoutingsCount": "Αριθμός δρομολογίων",
935935
"RuntimeVariant": "Παραλλαγή χρόνου εκτέλεσης εξαγωγής συμπερασμάτων",
936936
"SeeContainerLogs": "Βλέπε αρχεία καταγραφής κοντέινερ",
937+
"ServiceAlreadyExists": "Το όνομα της υπηρεσίας υπάρχει ήδη",
937938
"ServiceCreated": "Η υπηρεσία μοντέλου {{ όνομα }} δημιουργήθηκε με επιτυχία.",
938939
"ServiceDelegatedFrom": "Το μοντέλο υπηρεσίας δημιουργείται από τον {{ createdUser }}, αλλά η ιδιοκτησία της συνεδρίας θα ανατεθεί στον {{ sessionOwner }}",
939940
"ServiceEndpoint": "Τελικό σημείο υπηρεσίας",
940941
"ServiceInfo": "Πληροφορίες υπηρεσίας",
941942
"ServiceName": "Όνομα υπηρεσίας",
942943
"ServiceNameCannotStartWithHyphen": "Οι παύλες (-) δεν μπορούν να χρησιμοποιηθούν σε κανένα άκρο.",
944+
"ServiceNameInvalidCharacter": "Στη μέση του ονόματος της υπηρεσίας, μόνο το αλφάβητο, ο αριθμός, το '_', '.', '-'-'μπορεί να συμπεριληφθεί μόνο.",
943945
"ServiceNameMaxLength": "Εισαγάγετε 24 χαρακτήρες ή λιγότερους.",
944946
"ServiceNameMinLength": "Εισαγάγετε τουλάχιστον 4 χαρακτήρες.",
945-
"ServiceNameRule": "Επιτρέπονται μόνο αλφαριθμητικά, κάτω παύλα(_) και παύλα(-) και πρέπει να τελειώνει με αλφαριθμητικό χαρακτήρα.",
947+
"ServiceNameShouldEndWith": "Το όνομα της περιόδου σύνδεσης πρέπει να τελειώνει με αλφάβητο, μεγάλους και μικρούς χαρακτήρες, αριθμούς και «_».",
948+
"ServiceNameShouldStartWith": "Το όνομα της περιόδου σύνδεσης πρέπει να ξεκινά με αλφαβητικούς και μικρούς χαρακτήρες, αριθμούς ή '_'.",
946949
"ServiceTerminated": "Στάλθηκε ένα αίτημα τερματισμού λειτουργίας για την υπηρεσία μοντέλου \"{{ όνομα }}\".",
947950
"ServiceUpdated": "Η υπηρεσία μοντέλου {{ όνομα }} ενημερώθηκε με επιτυχία.",
948951
"Services": "Υπηρεσίες",

resources/i18n/en.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,15 +942,18 @@
942942
"RoutingsCount": "Routings Count",
943943
"RuntimeVariant": "Inference Runtime Variant",
944944
"SeeContainerLogs": "See Container Logs",
945+
"ServiceAlreadyExists": "Service name already exists",
945946
"ServiceCreated": "Model service {{ name }} has been created successfully.",
946947
"ServiceDelegatedFrom": "Model service is created by {{ createdUser }} but the session ownership will be delegated to {{ sessionOwner }}",
947948
"ServiceEndpoint": "Service Endpoint",
948949
"ServiceInfo": "Service Info",
949-
"ServiceName": "Service Name",
950+
"ServiceName": "Service name",
950951
"ServiceNameCannotStartWithHyphen": "Hyphens (-) cannot be used at either end.",
952+
"ServiceNameInvalidCharacter": "In the middle of the service name, only the alphabet, the number, the '_', '.', '-' can only be included.",
951953
"ServiceNameMaxLength": "Please enter 24 characters or less.",
952954
"ServiceNameMinLength": "Please enter at least 4 characters.",
953-
"ServiceNameRule": "Only allow alphanumeric, underscore(_), and hyphen(-) and it must end with an alphanumeric character.",
955+
"ServiceNameShouldEndWith": "It must end with alphabet, large and small characters, numbers, and '_'.",
956+
"ServiceNameShouldStartWith": "It must start with alphabetic and small characters, numbers or '_'.",
954957
"ServiceTerminated": "A shutdown request was sent for model service '{{ name }}'.",
955958
"ServiceUpdated": "Model service {{ name }} has been updated successfully.",
956959
"Services": "Services",

resources/i18n/es.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,15 +938,18 @@
938938
"RoutingsCount": "Recuento de rutas",
939939
"RuntimeVariant": "Variante de tiempo de ejecución de la inferencia",
940940
"SeeContainerLogs": "Ver registros de contenedores",
941+
"ServiceAlreadyExists": "El nombre del servicio ya existe",
941942
"ServiceCreated": "El servicio modelo {{ name }} se ha creado correctamente.",
942943
"ServiceDelegatedFrom": "El servicio modelo es creado por {{ createdUser }} pero la propiedad de la sesión será delegada a {{ sessionOwner }}",
943944
"ServiceEndpoint": "Punto final del servicio",
944945
"ServiceInfo": "Información de servicio",
945946
"ServiceName": "Nombre del servicio",
946947
"ServiceNameCannotStartWithHyphen": "No se pueden utilizar guiones (-) en ninguno de los extremos.",
948+
"ServiceNameInvalidCharacter": "En el medio del nombre del servicio, solo el alfabeto, el número, el '_', '.', '--' solo se puede incluir.",
947949
"ServiceNameMaxLength": "Por favor ingrese 24 caracteres o menos.",
948950
"ServiceNameMinLength": "Por favor ingrese al menos 4 caracteres.",
949-
"ServiceNameRule": "Solo se permiten caracteres alfanuméricos, guiones bajos (_) y guiones (-) y deben terminar con un carácter alfanumérico.",
951+
"ServiceNameShouldEndWith": "El nombre de la sesión debe terminar con alfabeto, caracteres grandes y pequeños, números y '_'.",
952+
"ServiceNameShouldStartWith": "El nombre de la sesión debe comenzar con caracteres alfabéticos y pequeños, números o '_'.",
950953
"ServiceTerminated": "Se envió una solicitud de cierre para el servicio modelo '{{ name }}'.",
951954
"ServiceUpdated": "El servicio de modelo {{ name }} se ha actualizado correctamente.",
952955
"Services": "Servicios",

resources/i18n/fi.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,15 +937,18 @@
937937
"RoutingsCount": "Reittien määrä",
938938
"RuntimeVariant": "Päättelyn suoritusaikamuunnos",
939939
"SeeContainerLogs": "Katso Säilön lokit",
940+
"ServiceAlreadyExists": "Palvelun nimi on jo olemassa",
940941
"ServiceCreated": "Mallipalvelun {{ name }} luominen onnistui.",
941942
"ServiceDelegatedFrom": "Mallipalvelun luo {{ createdUser }}, mutta istunnon omistusoikeus siirretään {{ sessionOwner }}:lle.",
942943
"ServiceEndpoint": "Palvelun päätepiste",
943944
"ServiceInfo": "Palvelun tiedot",
944945
"ServiceName": "Palvelun nimi",
945946
"ServiceNameCannotStartWithHyphen": "Tavuviivoja (-) ei voi käyttää kummassakaan päässä.",
947+
"ServiceNameInvalidCharacter": "Palvelun nimen keskellä vain aakkoset, numero '_', '.', '-' voidaan sisällyttää vain.",
946948
"ServiceNameMaxLength": "Anna enintään 24 merkkiä.",
947949
"ServiceNameMinLength": "Kirjoita vähintään 4 merkkiä.",
948-
"ServiceNameRule": "Salli vain aakkosnumeeriset, alaviivat (_) ja yhdysviivat (-), ja sen tulee päättyä aakkosnumeeriseen merkkiin.",
950+
"ServiceNameShouldEndWith": "Istunnon nimen on päätyttävä aakkosella, suurilla ja pienillä merkeillä, numeroilla ja '_'.",
951+
"ServiceNameShouldStartWith": "Istunnon nimen tulisi alkaa aakkoset ja pienet merkit, numerot tai '_'.",
949952
"ServiceTerminated": "Mallipalvelun '{{ name }}' sammutuspyyntö lähetettiin.",
950953
"ServiceUpdated": "Mallipalvelun {{ name }} päivitys onnistui.",
951954
"Services": "Palvelut",

0 commit comments

Comments
 (0)