mirror of https://github.com/portainer/portainer
fix(environments): debounce name validation [EE-4177] (#7889)
parent
9e1f80cf37
commit
9ef2e27aae
|
@ -14,7 +14,7 @@ import { FormControl } from '@@/form-components/FormControl';
|
|||
import { BoxSelector } from '@@/BoxSelector';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { NameField, nameValidation } from '../shared/NameField';
|
||||
import { NameField, useNameValidation } from '../shared/NameField';
|
||||
import { AnalyticsStateKey } from '../types';
|
||||
import { metadataValidation } from '../shared/MetadataFieldset/validation';
|
||||
import { MoreSettingsSection } from '../shared/MoreSettingsSection';
|
||||
|
@ -49,6 +49,7 @@ export function WizardAzure({ onCreate }: Props) {
|
|||
|
||||
const [creationType, setCreationType] = useState(options[0].id);
|
||||
const mutation = useCreateAzureEnvironmentMutation();
|
||||
const validation = useValidation();
|
||||
|
||||
return (
|
||||
<div className="form-horizontal">
|
||||
|
@ -64,7 +65,7 @@ export function WizardAzure({ onCreate }: Props) {
|
|||
onSubmit={handleSubmit}
|
||||
key={formKey}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}
|
||||
validationSchema={validation}
|
||||
>
|
||||
{({ errors, dirty, isValid }) => (
|
||||
<Form>
|
||||
|
@ -164,9 +165,9 @@ export function WizardAzure({ onCreate }: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function validationSchema(): SchemaOf<FormValues> {
|
||||
function useValidation(): SchemaOf<FormValues> {
|
||||
return object({
|
||||
name: nameValidation(),
|
||||
name: useNameValidation(),
|
||||
applicationId: string().required('Application ID is required'),
|
||||
tenantId: string().required('Tenant ID is required'),
|
||||
authenticationKey: string().required('Authentication Key is required'),
|
||||
|
|
|
@ -17,7 +17,7 @@ import { Icon } from '@@/Icon';
|
|||
import { NameField } from '../../shared/NameField';
|
||||
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
|
||||
|
||||
import { validation } from './APIForm.validation';
|
||||
import { useValidation } from './APIForm.validation';
|
||||
import { FormValues } from './types';
|
||||
import { TLSFieldset } from './TLSFieldset';
|
||||
|
||||
|
@ -42,6 +42,8 @@ export function APIForm({ onCreate }: Props) {
|
|||
EnvironmentCreationTypes.LocalDockerEnvironment
|
||||
);
|
||||
|
||||
const validation = useValidation();
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
|
|
|
@ -3,14 +3,14 @@ import { boolean, object, SchemaOf, string } from 'yup';
|
|||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
|
||||
import { nameValidation } from '../../shared/NameField';
|
||||
import { useNameValidation } from '../../shared/NameField';
|
||||
|
||||
import { validation as certsValidation } from './TLSFieldset';
|
||||
import { FormValues } from './types';
|
||||
|
||||
export function validation(): SchemaOf<FormValues> {
|
||||
export function useValidation(): SchemaOf<FormValues> {
|
||||
return object({
|
||||
name: nameValidation(),
|
||||
name: useNameValidation(),
|
||||
url: string().required('This field is required.'),
|
||||
tls: boolean().default(false),
|
||||
skipVerify: boolean(),
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Icon } from '@@/Icon';
|
|||
import { NameField } from '../../shared/NameField';
|
||||
import { MoreSettingsSection } from '../../shared/MoreSettingsSection';
|
||||
|
||||
import { validation } from './SocketForm.validation';
|
||||
import { useValidation } from './SocketForm.validation';
|
||||
import { FormValues } from './types';
|
||||
|
||||
interface Props {
|
||||
|
@ -33,6 +33,7 @@ export function SocketForm({ onCreate }: Props) {
|
|||
};
|
||||
|
||||
const mutation = useCreateLocalDockerEnvironmentMutation();
|
||||
const validation = useValidation();
|
||||
|
||||
return (
|
||||
<Formik
|
||||
|
|
|
@ -3,13 +3,13 @@ import { boolean, object, SchemaOf, string } from 'yup';
|
|||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
|
||||
import { nameValidation } from '../../shared/NameField';
|
||||
import { useNameValidation } from '../../shared/NameField';
|
||||
|
||||
import { FormValues } from './types';
|
||||
|
||||
export function validation(): SchemaOf<FormValues> {
|
||||
export function useValidation(): SchemaOf<FormValues> {
|
||||
return object({
|
||||
name: nameValidation(),
|
||||
name: useNameValidation(),
|
||||
meta: metadataValidation(),
|
||||
overridePath: boolean().default(false),
|
||||
socketPath: string()
|
||||
|
|
|
@ -14,7 +14,7 @@ import { MoreSettingsSection } from '../MoreSettingsSection';
|
|||
import { Hardware } from '../Hardware/Hardware';
|
||||
|
||||
import { EnvironmentUrlField } from './EnvironmentUrlField';
|
||||
import { validation } from './AgentForm.validation';
|
||||
import { useValidation } from './AgentForm.validation';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
|
@ -35,6 +35,7 @@ export function AgentForm({ onCreate, showGpus = false }: Props) {
|
|||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
|
||||
const mutation = useCreateAgentEnvironmentMutation();
|
||||
const validation = useValidation();
|
||||
|
||||
return (
|
||||
<Formik
|
||||
|
|
|
@ -4,11 +4,11 @@ import { gpusListValidation } from '@/react/portainer/environments/wizard/Enviro
|
|||
import { CreateAgentEnvironmentValues } from '@/react/portainer/environments/environment.service/create';
|
||||
|
||||
import { metadataValidation } from '../MetadataFieldset/validation';
|
||||
import { nameValidation } from '../NameField';
|
||||
import { useNameValidation } from '../NameField';
|
||||
|
||||
export function validation(): SchemaOf<CreateAgentEnvironmentValues> {
|
||||
export function useValidation(): SchemaOf<CreateAgentEnvironmentValues> {
|
||||
return object({
|
||||
name: nameValidation(),
|
||||
name: useNameValidation(),
|
||||
environmentUrl: environmentValidation(),
|
||||
meta: metadataValidation(),
|
||||
gpus: gpusListValidation(),
|
||||
|
|
|
@ -14,7 +14,7 @@ import { MoreSettingsSection } from '../../MoreSettingsSection';
|
|||
import { Hardware } from '../../Hardware/Hardware';
|
||||
|
||||
import { EdgeAgentFieldset } from './EdgeAgentFieldset';
|
||||
import { validationSchema } from './EdgeAgentForm.validation';
|
||||
import { useValidationSchema } from './EdgeAgentForm.validation';
|
||||
import { FormValues } from './types';
|
||||
|
||||
interface Props {
|
||||
|
@ -29,13 +29,14 @@ export function EdgeAgentForm({ onCreate, readonly, showGpus = false }: Props) {
|
|||
const createEdgeDevice = useCreateEdgeDeviceParam();
|
||||
|
||||
const createMutation = useCreateEdgeAgentEnvironmentMutation();
|
||||
const validation = useValidationSchema();
|
||||
|
||||
return (
|
||||
<Formik<FormValues>
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}
|
||||
validationSchema={validation}
|
||||
>
|
||||
{({ isValid, setFieldValue, values }) => (
|
||||
<Form>
|
||||
|
|
|
@ -3,14 +3,16 @@ import { number, object, SchemaOf } from 'yup';
|
|||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { metadataValidation } from '../../MetadataFieldset/validation';
|
||||
import { nameValidation } from '../../NameField';
|
||||
import { useNameValidation } from '../../NameField';
|
||||
|
||||
import { validation as urlValidation } from './PortainerUrlField';
|
||||
import { FormValues } from './types';
|
||||
|
||||
export function validationSchema(): SchemaOf<FormValues> {
|
||||
export function useValidationSchema(): SchemaOf<FormValues> {
|
||||
const nameValidation = useNameValidation();
|
||||
|
||||
return object().shape({
|
||||
name: nameValidation(),
|
||||
name: nameValidation,
|
||||
portainerUrl: urlValidation(),
|
||||
pollFrequency: number().required(),
|
||||
meta: metadataValidation(),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Field, useField } from 'formik';
|
||||
import { string } from 'yup';
|
||||
import { debounce } from 'lodash';
|
||||
import { useRef } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { getEnvironments } from '@/react/portainer/environments/environment.service';
|
||||
|
||||
|
@ -30,7 +31,7 @@ export function NameField({ readonly }: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export async function isNameUnique(name?: string) {
|
||||
export async function isNameUnique(name = '') {
|
||||
if (!name) {
|
||||
return true;
|
||||
}
|
||||
|
@ -46,14 +47,26 @@ export async function isNameUnique(name?: string) {
|
|||
return true;
|
||||
}
|
||||
|
||||
const debouncedIsNameUnique = debounce(isNameUnique, 500);
|
||||
function cacheTest(
|
||||
asyncValidate: (val?: string) => Promise<boolean> | undefined
|
||||
) {
|
||||
let valid = false;
|
||||
let value = '';
|
||||
|
||||
return async (newValue = '') => {
|
||||
if (newValue !== value) {
|
||||
const response = await asyncValidate(newValue);
|
||||
value = newValue;
|
||||
valid = !!response;
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
}
|
||||
|
||||
export function useNameValidation() {
|
||||
const uniquenessTest = useRef(cacheTest(_.debounce(isNameUnique, 300)));
|
||||
|
||||
export function nameValidation() {
|
||||
return string()
|
||||
.required('Name is required')
|
||||
.test(
|
||||
'unique-name',
|
||||
'Name should be unique',
|
||||
(name) => debouncedIsNameUnique(name) || false
|
||||
);
|
||||
.test('unique-name', 'Name should be unique', uniquenessTest.current);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue