Fix #16471: Form validation message and alert bug fixes and improvements (#16761)

* Fix the styling issue for the teams and user selection component in the alert form

* Fix the incorrect field names in form error messages

* Fix and modify cypress tests

* Fix the incorrect util function import

* update the field name in form validation message

* Update proper assertions
This commit is contained in:
Aniket Katkar 2024-06-27 15:47:51 +05:30 committed by GitHub
parent fd942a27a6
commit 7a68ee30e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 279 additions and 297 deletions

View File

@ -13,7 +13,7 @@
import { import {
DELETE_TERM, DELETE_TERM,
INVALID_NAMES, INVALID_NAMES,
NAME_MAX_LENGTH_VALIDATION_ERROR, NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128,
NAME_VALIDATION_ERROR, NAME_VALIDATION_ERROR,
SEARCH_ENTITY_TABLE, SEARCH_ENTITY_TABLE,
} from '../constants/constants'; } from '../constants/constants';
@ -47,9 +47,10 @@ export const validateDomainForm = () => {
.scrollIntoView() .scrollIntoView()
.should('be.visible') .should('be.visible')
.type(INVALID_NAMES.MAX_LENGTH); .type(INVALID_NAMES.MAX_LENGTH);
cy.get('#name_help') cy.get('#name_help').should(
.should('be.visible') 'contain',
.contains(NAME_MAX_LENGTH_VALIDATION_ERROR); NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128
);
// with special char validation // with special char validation
cy.get('[data-testid="name"]') cy.get('[data-testid="name"]')

View File

@ -14,7 +14,7 @@
import { import {
DELETE_TERM, DELETE_TERM,
INVALID_NAMES, INVALID_NAMES,
NAME_MAX_LENGTH_VALIDATION_ERROR, NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128,
NAME_VALIDATION_ERROR, NAME_VALIDATION_ERROR,
} from '../constants/constants'; } from '../constants/constants';
import { SidebarItem } from '../constants/Entity.interface'; import { SidebarItem } from '../constants/Entity.interface';
@ -40,9 +40,10 @@ export const validateForm = () => {
.scrollIntoView() .scrollIntoView()
.should('be.visible') .should('be.visible')
.type(INVALID_NAMES.MAX_LENGTH); .type(INVALID_NAMES.MAX_LENGTH);
cy.get('#name_help') cy.get('#name_help').should(
.should('be.visible') 'contain',
.contains(NAME_MAX_LENGTH_VALIDATION_ERROR); NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128
);
// with special char validation // with special char validation
cy.get('[data-testid="name"]') cy.get('[data-testid="name"]')

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EXAMPLE_LONG_STRING } from '../../constants/constants';
export const validateFormNameFieldInput = ({
fieldSelector = '#name',
checkEmpty = true,
checkLong = true,
fieldName,
errorDivSelector,
value,
}: {
value: string;
fieldName: string;
errorDivSelector: string;
fieldSelector?: string;
checkEmpty?: boolean;
checkLong?: boolean;
}) => {
if (checkEmpty) {
// Check empty name field message
cy.get(fieldSelector).type('test');
cy.get(fieldSelector).clear();
cy.get(`${errorDivSelector} .ant-form-item-explain-error`).contains(
`${fieldName} is required`
);
}
if (checkLong) {
// Check long name field message
cy.get(fieldSelector).type(EXAMPLE_LONG_STRING);
cy.get(`${errorDivSelector} .ant-form-item-explain-error`).contains(
`${fieldName} size must be between 1 and 128`
);
cy.get(fieldSelector).clear();
}
cy.get(fieldSelector).type(value);
};

View File

@ -17,6 +17,7 @@ import {
toastNotification, toastNotification,
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../common'; } from '../common';
import { validateFormNameFieldInput } from './Form';
const TEAM_TYPES = ['Department', 'Division', 'Group']; const TEAM_TYPES = ['Department', 'Division', 'Group'];
@ -179,7 +180,12 @@ export const addTeam = (
verifyResponseStatusCode('@addTeam', 200); verifyResponseStatusCode('@addTeam', 200);
// Entering team details // Entering team details
cy.get('[data-testid="name"]').type(teamDetails.name); validateFormNameFieldInput({
value: teamDetails.name,
fieldName: 'Name',
fieldSelector: '[data-testid="name"]',
errorDivSelector: '#add-team-nest-messages_name_help',
});
cy.get('[data-testid="display-name"]').type(teamDetails.name); cy.get('[data-testid="display-name"]').type(teamDetails.name);

View File

@ -621,9 +621,8 @@ export const NAME_VALIDATION_ERROR =
export const NAME_MIN_MAX_LENGTH_VALIDATION_ERROR = export const NAME_MIN_MAX_LENGTH_VALIDATION_ERROR =
'Name size must be between 2 and 64'; 'Name size must be between 2 and 64';
export const NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128 =
export const NAME_MAX_LENGTH_VALIDATION_ERROR = 'Name size must be between 1 and 128';
'Name can be a maximum of 128 characters';
export const DOMAIN_1 = { export const DOMAIN_1 = {
name: 'Cypress%Domain', name: 'Cypress%Domain',
@ -836,3 +835,5 @@ export const JWT_EXPIRY_TIME_MAP = {
'1 hour': 3600, '1 hour': 3600,
'2 hours': 7200, '2 hours': 7200,
}; };
export const EXAMPLE_LONG_STRING = 'name'.repeat(33); // 132 characters

View File

@ -20,7 +20,7 @@ import { visitEntityDetailsPage } from '../../common/Utils/Entity';
import { getToken } from '../../common/Utils/LocalStorage'; import { getToken } from '../../common/Utils/LocalStorage';
import { import {
INVALID_NAMES, INVALID_NAMES,
NAME_MAX_LENGTH_VALIDATION_ERROR, NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128,
NAME_VALIDATION_ERROR, NAME_VALIDATION_ERROR,
uuid, uuid,
} from '../../constants/constants'; } from '../../constants/constants';
@ -53,7 +53,10 @@ const validateForm = (isColumnMetric = false) => {
// max length validation // max length validation
cy.get('#name').scrollIntoView().type(INVALID_NAMES.MAX_LENGTH); cy.get('#name').scrollIntoView().type(INVALID_NAMES.MAX_LENGTH);
cy.get('#name_help').should('contain', NAME_MAX_LENGTH_VALIDATION_ERROR); cy.get('#name_help').should(
'contain',
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128
);
// with special char validation // with special char validation
cy.get('#name') cy.get('#name')

View File

@ -17,6 +17,7 @@ import {
uuid, uuid,
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../../common/common'; } from '../../common/common';
import { validateFormNameFieldInput } from '../../common/Utils/Form';
import { BASE_URL } from '../../constants/constants'; import { BASE_URL } from '../../constants/constants';
import { GlobalSettingOptions } from '../../constants/settings.constant'; import { GlobalSettingOptions } from '../../constants/settings.constant';
@ -44,7 +45,11 @@ describe(
cy.get('[data-testid="inactive-link"]').should('contain', 'Add New Role'); cy.get('[data-testid="inactive-link"]').should('contain', 'Add New Role');
// Entering name // Entering name
cy.get('#name').type(roleName); validateFormNameFieldInput({
value: roleName,
fieldName: 'Name',
errorDivSelector: '#name_help',
});
// Entering descrription // Entering descrription
cy.get(descriptionBox).type('description'); cy.get(descriptionBox).type('description');
// Select the policies // Select the policies

View File

@ -33,6 +33,7 @@ import {
createSingleLevelEntity, createSingleLevelEntity,
hardDeleteService, hardDeleteService,
} from '../../common/EntityUtils'; } from '../../common/EntityUtils';
import { validateFormNameFieldInput } from '../../common/Utils/Form';
import { getToken } from '../../common/Utils/LocalStorage'; import { getToken } from '../../common/Utils/LocalStorage';
import { import {
ALERT_DESCRIPTION, ALERT_DESCRIPTION,
@ -155,7 +156,11 @@ describe(
cy.get('[data-testid="create-notification"]').click(); cy.get('[data-testid="create-notification"]').click();
// Enter alert name // Enter alert name
cy.get('#name').type(ALERT_NAME); validateFormNameFieldInput({
value: ALERT_NAME,
fieldName: 'Name',
errorDivSelector: '#name_help',
});
// Enter description // Enter description
cy.get(descriptionBox).clear().type(ALERT_DESCRIPTION); cy.get(descriptionBox).clear().type(ALERT_DESCRIPTION);

View File

@ -29,6 +29,7 @@ import {
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../../common/common'; } from '../../common/common';
import { createEntityTable, hardDeleteService } from '../../common/EntityUtils'; import { createEntityTable, hardDeleteService } from '../../common/EntityUtils';
import { validateFormNameFieldInput } from '../../common/Utils/Form';
import { getToken } from '../../common/Utils/LocalStorage'; import { getToken } from '../../common/Utils/LocalStorage';
import { import {
ALERT_DESCRIPTION, ALERT_DESCRIPTION,
@ -371,7 +372,11 @@ describe(
cy.get('[data-testid="create-observability"]').click(); cy.get('[data-testid="create-observability"]').click();
// Enter alert name // Enter alert name
cy.get('#name').type(ALERT_NAME); validateFormNameFieldInput({
value: ALERT_NAME,
fieldName: 'Name',
errorDivSelector: '#name_help',
});
// Enter description // Enter description
cy.get(descriptionBox).clear().type(ALERT_DESCRIPTION); cy.get(descriptionBox).clear().type(ALERT_DESCRIPTION);

View File

@ -17,6 +17,7 @@ import {
toastNotification, toastNotification,
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../../common/common'; } from '../../common/common';
import { validateFormNameFieldInput } from '../../common/Utils/Form';
import { getToken } from '../../common/Utils/LocalStorage'; import { getToken } from '../../common/Utils/LocalStorage';
import { DELETE_TERM } from '../../constants/constants'; import { DELETE_TERM } from '../../constants/constants';
import { PERSONA_DETAILS, USER_DETAILS } from '../../constants/EntityConstant'; import { PERSONA_DETAILS, USER_DETAILS } from '../../constants/EntityConstant';
@ -41,7 +42,12 @@ const updatePersonaDisplayName = (displayName) => {
}; };
describe('Persona operations', { tags: 'Settings' }, () => { describe('Persona operations', { tags: 'Settings' }, () => {
const user = {}; const user = {
details: {
id: '',
name: '',
},
};
const userSearchText = `${USER_DETAILS.firstName}${USER_DETAILS.lastName}`; const userSearchText = `${USER_DETAILS.firstName}${USER_DETAILS.lastName}`;
before(() => { before(() => {
cy.login(); cy.login();
@ -87,6 +93,12 @@ describe('Persona operations', { tags: 'Settings' }, () => {
it('Persona creation should work properly', () => { it('Persona creation should work properly', () => {
cy.get('[data-testid="add-persona-button"]').scrollIntoView().click(); cy.get('[data-testid="add-persona-button"]').scrollIntoView().click();
cy.get('[data-testid="name"]').clear().type(PERSONA_DETAILS.name); cy.get('[data-testid="name"]').clear().type(PERSONA_DETAILS.name);
validateFormNameFieldInput({
value: PERSONA_DETAILS.name,
fieldName: 'Name',
fieldSelector: '[data-testid="name"]',
errorDivSelector: '#name_help',
});
cy.get('[data-testid="displayName"]') cy.get('[data-testid="displayName"]')
.clear() .clear()
.type(PERSONA_DETAILS.displayName); .type(PERSONA_DETAILS.displayName);

View File

@ -17,6 +17,7 @@ import {
uuid, uuid,
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../../common/common'; } from '../../common/common';
import { validateFormNameFieldInput } from '../../common/Utils/Form';
import { BASE_URL } from '../../constants/constants'; import { BASE_URL } from '../../constants/constants';
import { GlobalSettingOptions } from '../../constants/settings.constant'; import { GlobalSettingOptions } from '../../constants/settings.constant';
@ -58,7 +59,12 @@ const newRuledescription = `This is ${newRuleName} description`;
const updatedRuleName = `New-Rule-test-${uuid()}-updated`; const updatedRuleName = `New-Rule-test-${uuid()}-updated`;
const addRule = (rulename, ruleDescription, descriptionIndex) => { const addRule = (rulename, ruleDescription, descriptionIndex) => {
cy.get('[data-testid="rule-name"]').should('be.visible').type(rulename); validateFormNameFieldInput({
value: rulename,
fieldName: 'Name',
fieldSelector: '[data-testid="rule-name"]',
errorDivSelector: '#ruleName_help',
});
// Enter rule description // Enter rule description
cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror') cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror')
.eq(descriptionIndex) .eq(descriptionIndex)
@ -127,6 +133,12 @@ describe('Policy page should work properly', { tags: 'Settings' }, () => {
// Enter policy name // Enter policy name
cy.get('[data-testid="policy-name"]').should('be.visible').type(policyName); cy.get('[data-testid="policy-name"]').should('be.visible').type(policyName);
validateFormNameFieldInput({
value: policyName,
fieldName: 'Name',
fieldSelector: '[data-testid="policy-name"]',
errorDivSelector: '#name_help',
});
// Enter description // Enter description
cy.get(descriptionBox).eq(0).type(description); cy.get(descriptionBox).eq(0).type(description);

View File

@ -123,7 +123,17 @@ const PopoverContent: FC<PopoverContentProps> = ({
onFinish={handleEmbedImage}> onFinish={handleEmbedImage}>
<Row gutter={[8, 8]}> <Row gutter={[8, 8]}>
<Col span={24}> <Col span={24}>
<Form.Item name="Url" rules={[{ required: true, type: 'url' }]}> <Form.Item
name="Url"
rules={[
{
required: true,
type: 'url',
message: t('label.field-required', {
field: t('label.url-uppercase'),
}),
},
]}>
<Input <Input
autoFocus autoFocus
data-testid="embed-input" data-testid="embed-input"

View File

@ -15,7 +15,8 @@ import QueryString from 'qs';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants'; import { VALIDATION_MESSAGES } from '../../../constants/constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { CSMode } from '../../../enums/codemirror.enum'; import { CSMode } from '../../../enums/codemirror.enum';
import { CustomMetric } from '../../../generated/entity/data/table'; import { CustomMetric } from '../../../generated/entity/data/table';
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
@ -76,29 +77,13 @@ const CustomMetricForm = ({
data-testid="custom-metric-form" data-testid="custom-metric-form"
form={form} form={form}
layout="vertical" layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={onFinish}> onFinish={onFinish}>
<Form.Item <Form.Item
label={t('label.name')} label={t('label.name')}
name="name" name="name"
rules={[ rules={[
{ ...NAME_FIELD_RULES,
required: true,
message: t('label.field-required', {
field: t('label.name'),
}),
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
{
min: 1,
max: 128,
message: `${t('message.entity-maximum-size', {
entity: `${t('label.name')}`,
max: '128',
})}`,
},
{ {
validator: (_, value) => { validator: (_, value) => {
if (metricNames.includes(value) && !isEditMode) { if (metricNames.includes(value) && !isEditMode) {
@ -126,9 +111,6 @@ const CustomMetricForm = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.column'),
}),
}, },
]}> ]}>
<Select <Select
@ -152,9 +134,6 @@ const CustomMetricForm = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.sql-uppercase-query'),
}),
}, },
]} ]}
trigger="onChange"> trigger="onChange">

View File

@ -28,6 +28,7 @@ import { getEntityReferenceFromEntity } from '../../../utils/EntityUtils';
import { fetchOptions, generateOptions } from '../../../utils/TasksUtils'; import { fetchOptions, generateOptions } from '../../../utils/TasksUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import { VALIDATION_MESSAGES } from '../../../constants/constants';
import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useApplicationStore } from '../../../hooks/useApplicationStore';
import { TestCaseStatusModalProps } from './TestCaseStatusModal.interface'; import { TestCaseStatusModalProps } from './TestCaseStatusModal.interface';
@ -162,6 +163,7 @@ export const TestCaseStatusModal = ({
id="update-status-form" id="update-status-form"
initialValues={data} initialValues={data}
layout="vertical" layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={handleFormSubmit}> onFinish={handleFormSubmit}>
<Form.Item <Form.Item
label={t('label.status')} label={t('label.status')}
@ -169,9 +171,6 @@ export const TestCaseStatusModal = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.status'),
}),
}, },
]}> ]}>
<Select <Select
@ -193,9 +192,6 @@ export const TestCaseStatusModal = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.reason'),
}),
}, },
]}> ]}>
<Select <Select
@ -217,9 +213,6 @@ export const TestCaseStatusModal = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.comment'),
}),
}, },
]}> ]}>
<RichTextEditor <RichTextEditor
@ -252,9 +245,6 @@ export const TestCaseStatusModal = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.assignee'),
}),
}, },
]}> ]}>
<Assignees <Assignees

View File

@ -19,7 +19,7 @@ import {
PAGE_SIZE_MEDIUM, PAGE_SIZE_MEDIUM,
VALIDATION_MESSAGES, VALIDATION_MESSAGES,
} from '../../../../constants/constants'; } from '../../../../constants/constants';
import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants'; import { NAME_FIELD_RULES } from '../../../../constants/Form.constants';
import { TestSuite } from '../../../../generated/tests/testSuite'; import { TestSuite } from '../../../../generated/tests/testSuite';
import { DataQualityPageTabs } from '../../../../pages/DataQuality/DataQualityPage.interface'; import { DataQualityPageTabs } from '../../../../pages/DataQuality/DataQualityPage.interface';
import { getListTestSuites } from '../../../../rest/testAPI'; import { getListTestSuites } from '../../../../rest/testAPI';
@ -78,22 +78,7 @@ const AddTestSuiteForm: React.FC<AddTestSuiteFormProps> = ({
label={t('label.name')} label={t('label.name')}
name="name" name="name"
rules={[ rules={[
{ ...NAME_FIELD_RULES,
required: true,
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
{
min: 1,
max: 256,
message: `${t('message.entity-size-in-between', {
entity: `${t('label.name')}`,
max: '256',
min: '1',
})}`,
},
{ {
validator: (_, value) => { validator: (_, value) => {
if (testSuites.some((suite) => suite.name === value)) { if (testSuites.some((suite) => suite.name === value)) {

View File

@ -17,10 +17,8 @@ import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { UserTag } from '../../../components/common/UserTag/UserTag.component'; import { UserTag } from '../../../components/common/UserTag/UserTag.component';
import { UserTagSize } from '../../../components/common/UserTag/UserTag.interface'; import { UserTagSize } from '../../../components/common/UserTag/UserTag.interface';
import { import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
ENTITY_NAME_REGEX, import { HEX_COLOR_CODE_REGEX } from '../../../constants/regex.constants';
HEX_COLOR_CODE_REGEX,
} from '../../../constants/regex.constants';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider'; import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface'; import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface';
import { CreateDataProduct } from '../../../generated/api/domains/createDataProduct'; import { CreateDataProduct } from '../../../generated/api/domains/createDataProduct';
@ -72,20 +70,7 @@ const AddDomainForm = ({
props: { props: {
'data-testid': 'name', 'data-testid': 'name',
}, },
rules: [ rules: NAME_FIELD_RULES,
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
{
min: 1,
max: 128,
message: `${t('message.entity-maximum-size', {
entity: `${t('label.name')}`,
max: '128',
})}`,
},
],
}, },
{ {
name: 'displayName', name: 'displayName',

View File

@ -16,7 +16,6 @@ import { Button, Form, Space, Typography } from 'antd';
import { FormProps, useForm } from 'antd/lib/form/Form'; import { FormProps, useForm } from 'antd/lib/form/Form';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import { import {
CreateGlossary, CreateGlossary,
EntityReference, EntityReference,
@ -29,6 +28,7 @@ import {
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../utils/formUtils'; import { generateFormFields, getField } from '../../../utils/formUtils';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useApplicationStore } from '../../../hooks/useApplicationStore';
import ResizablePanels from '../../common/ResizablePanels/ResizablePanels'; import ResizablePanels from '../../common/ResizablePanels/ResizablePanels';
@ -104,20 +104,7 @@ const AddGlossary = ({
props: { props: {
'data-testid': 'name', 'data-testid': 'name',
}, },
rules: [ rules: NAME_FIELD_RULES,
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
{
min: 1,
max: 128,
message: `${t('message.entity-maximum-size', {
entity: `${t('label.name')}`,
max: '128',
})}`,
},
],
}, },
{ {
name: 'displayName', name: 'displayName',

View File

@ -17,10 +17,7 @@ import { t } from 'i18next';
import { isEmpty, isString } from 'lodash'; import { isEmpty, isString } from 'lodash';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { ReactComponent as DeleteIcon } from '../../../assets/svg/ic-delete.svg'; import { ReactComponent as DeleteIcon } from '../../../assets/svg/ic-delete.svg';
import { import { HEX_COLOR_CODE_REGEX } from '../../../constants/regex.constants';
ENTITY_NAME_REGEX,
HEX_COLOR_CODE_REGEX,
} from '../../../constants/regex.constants';
import { EntityReference } from '../../../generated/entity/type'; import { EntityReference } from '../../../generated/entity/type';
import { import {
FieldProp, FieldProp,
@ -31,6 +28,7 @@ import { getEntityName } from '../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../utils/formUtils'; import { generateFormFields, getField } from '../../../utils/formUtils';
import { fetchGlossaryList } from '../../../utils/TagsUtils'; import { fetchGlossaryList } from '../../../utils/TagsUtils';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useApplicationStore } from '../../../hooks/useApplicationStore';
import { UserTeam } from '../../common/AssigneeList/AssigneeList.interface'; import { UserTeam } from '../../common/AssigneeList/AssigneeList.interface';
import { UserTag } from '../../common/UserTag/UserTag.component'; import { UserTag } from '../../common/UserTag/UserTag.component';
@ -167,20 +165,7 @@ const AddGlossaryTermForm = ({
props: { props: {
'data-testid': 'name', 'data-testid': 'name',
}, },
rules: [ rules: NAME_FIELD_RULES,
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
{
min: 1,
max: 128,
message: `${t('message.entity-maximum-size', {
entity: `${t('label.name')}`,
max: '128',
})}`,
},
],
}, },
{ {
name: 'displayName', name: 'displayName',

View File

@ -15,10 +15,7 @@ import { Form, Modal, Select } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import { API_RES_MAX_SIZE } from '../../../constants/constants';
API_RES_MAX_SIZE,
VALIDATION_MESSAGES,
} from '../../../constants/constants';
import { getGlossaryTerms } from '../../../rest/glossaryAPI'; import { getGlossaryTerms } from '../../../rest/glossaryAPI';
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
@ -90,7 +87,6 @@ const ChangeParentHierarchy = ({
form={form} form={form}
id="change-parent-hierarchy-modal" id="change-parent-hierarchy-modal"
layout="vertical" layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSubmit}> onFinish={handleSubmit}>
<Form.Item <Form.Item
label={t('label.select-field', { label={t('label.select-field', {
@ -100,6 +96,9 @@ const ChangeParentHierarchy = ({
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.parent'),
}),
}, },
]}> ]}>
<Select <Select

View File

@ -20,7 +20,7 @@ import { isEmpty } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { VALIDATION_MESSAGES } from '../../../../constants/constants'; import { VALIDATION_MESSAGES } from '../../../../constants/constants';
import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants'; import { NAME_FIELD_RULES } from '../../../../constants/Form.constants';
import { Persona } from '../../../../generated/entity/teams/persona'; import { Persona } from '../../../../generated/entity/teams/persona';
import { EntityReference } from '../../../../generated/entity/type'; import { EntityReference } from '../../../../generated/entity/type';
import { import {
@ -85,12 +85,7 @@ export const AddEditPersonaForm = ({
autoComplete: 'off', autoComplete: 'off',
}, },
placeholder: t('label.name'), placeholder: t('label.name'),
rules: [ rules: NAME_FIELD_RULES,
{
pattern: ENTITY_NAME_REGEX,
message: t('message.custom-property-name-validation'),
},
],
}, },
{ {
name: 'displayName', name: 'displayName',

View File

@ -15,6 +15,7 @@ import { Button, Form, FormProps, Select, Space } from 'antd';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { FC, useMemo } from 'react'; import React, { FC, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { VALIDATION_MESSAGES } from '../../../../constants/constants';
import { import {
PersonalAccessToken, PersonalAccessToken,
TokenType, TokenType,
@ -85,67 +86,62 @@ const AuthMechanismForm: FC<Props> = ({
}, [isBot, authenticationMechanism]); }, [isBot, authenticationMechanism]);
return ( return (
<> <Form
<Form id="update-auth-mechanism-form"
id="update-auth-mechanism-form" initialValues={{ authType, tokenExpiry }}
initialValues={{ authType, tokenExpiry }} layout="vertical"
layout="vertical" validateMessages={VALIDATION_MESSAGES}
onFinish={handleSave}> onFinish={handleSave}>
<Form.Item label={t('label.auth-mechanism')} name="authType"> <Form.Item label={t('label.auth-mechanism')} name="authType">
<Select <Select
disabled disabled
className="w-full" className="w-full"
data-testid="auth-mechanism" data-testid="auth-mechanism"
placeholder={t('label.select-field', { placeholder={t('label.select-field', {
field: t('label.auth-mechanism'), field: t('label.auth-mechanism'),
})}> })}>
<Option key={authOptions.value}>{authOptions.label}</Option> <Option key={authOptions.value}>{authOptions.label}</Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t('label.token-expiration')} label={t('label.token-expiration')}
name="tokenExpiry" name="tokenExpiry"
rules={[ rules={[{ required: true }]}>
{ <Select
required: true, className="w-full"
}, data-testid="token-expiry"
]}> placeholder={t('message.select-token-expiration')}>
<Select {isBot
className="w-full" ? getJWTTokenExpiryOptions().map((option) => (
data-testid="token-expiry" <Option key={option.value}>{option.label}</Option>
placeholder={t('message.select-token-expiration')}> ))
{isBot : getJWTTokenExpiryOptions()
? getJWTTokenExpiryOptions().map((option) => ( .filter((option) => option.value !== 'Unlimited')
<Option key={option.value}>{option.label}</Option> .map((filteredOption) => (
)) <Option key={filteredOption.value}>
: getJWTTokenExpiryOptions() {filteredOption.label}
.filter((option) => option.value !== 'Unlimited') </Option>
.map((filteredOption) => ( ))}
<Option key={filteredOption.value}> </Select>
{filteredOption.label} </Form.Item>
</Option>
))}
</Select>
</Form.Item>
<Space className="w-full justify-end" size={4}> <Space className="w-full justify-end" size={4}>
{!isEmpty(authenticationMechanism) && ( {!isEmpty(authenticationMechanism) && (
<Button data-testid="cancel-edit" type="link" onClick={onCancel}> <Button data-testid="cancel-edit" type="link" onClick={onCancel}>
{t('label.cancel')} {t('label.cancel')}
</Button>
)}
<Button
data-testid="save-edit"
form="update-auth-mechanism-form"
htmlType="submit"
loading={isUpdating}
type="primary">
{t('label.generate')}
</Button> </Button>
</Space> )}
</Form> <Button
</> data-testid="save-edit"
form="update-auth-mechanism-form"
htmlType="submit"
loading={isUpdating}
type="primary">
{t('label.generate')}
</Button>
</Space>
</Form>
); );
}; };

View File

@ -549,6 +549,9 @@ const CronEditor: FC<CronEditorProp> = (props) => {
rules={[ rules={[
{ {
required: true, required: true,
message: t('label.field-required', {
field: t('label.cron'),
}),
}, },
{ {
validator: async (_, value) => { validator: async (_, value) => {

View File

@ -0,0 +1,38 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Rule } from 'antd/lib/form';
import { t } from 'i18next';
import { ENTITY_NAME_REGEX } from './regex.constants';
export const NAME_FIELD_RULES: Rule[] = [
{
required: true,
message: t('label.field-required', {
field: t('label.name'),
}),
},
{
min: 1,
max: 128,
message: t('message.entity-size-in-between', {
entity: t('label.name'),
min: 1,
max: 128,
}),
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
];

View File

@ -21,9 +21,9 @@ import Loader from '../../components/common/Loader/Loader';
import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels'; import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels';
import RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor'; import RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor';
import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component'; import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ROUTES } from '../../constants/constants'; import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants';
import { NAME_FIELD_RULES } from '../../constants/Form.constants';
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants'; import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
import { ENTITY_NAME_REGEX } from '../../constants/regex.constants';
import { CreateEventSubscription } from '../../generated/events/api/createEventSubscription'; import { CreateEventSubscription } from '../../generated/events/api/createEventSubscription';
import { import {
AlertType, AlertType,
@ -212,6 +212,7 @@ const AddNotificationPage = () => {
...alert, ...alert,
resources: alert?.filteringRules?.resources, resources: alert?.filteringRules?.resources,
}} }}
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSave}> onFinish={handleSave}>
{loadingCount > 0 ? ( {loadingCount > 0 ? (
<Skeleton title paragraph={{ rows: 8 }} /> <Skeleton title paragraph={{ rows: 8 }} />
@ -222,13 +223,7 @@ const AddNotificationPage = () => {
label={t('label.name')} label={t('label.name')}
labelCol={{ span: 24 }} labelCol={{ span: 24 }}
name="name" name="name"
rules={[ rules={NAME_FIELD_RULES}>
{ required: true },
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
<Input <Input
disabled={isEditMode} disabled={isEditMode}
placeholder={t('label.name')} placeholder={t('label.name')}

View File

@ -21,8 +21,8 @@ import Loader from '../../components/common/Loader/Loader';
import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels'; import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels';
import RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor'; import RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor';
import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component'; import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ROUTES } from '../../constants/constants'; import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants';
import { ENTITY_NAME_REGEX } from '../../constants/regex.constants'; import { NAME_FIELD_RULES } from '../../constants/Form.constants';
import { CreateEventSubscription } from '../../generated/events/api/createEventSubscription'; import { CreateEventSubscription } from '../../generated/events/api/createEventSubscription';
import { import {
AlertType, AlertType,
@ -217,6 +217,7 @@ function AddObservabilityPage() {
...alert, ...alert,
resources: alert?.filteringRules?.resources, resources: alert?.filteringRules?.resources,
}} }}
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSave}> onFinish={handleSave}>
<Row gutter={[20, 20]}> <Row gutter={[20, 20]}>
<Col span={24}> <Col span={24}>
@ -224,13 +225,7 @@ function AddObservabilityPage() {
label={t('label.name')} label={t('label.name')}
labelCol={{ span: 24 }} labelCol={{ span: 24 }}
name="name" name="name"
rules={[ rules={NAME_FIELD_RULES}>
{ required: true },
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
<Input <Input
disabled={isEditMode} disabled={isEditMode}
placeholder={t('label.name')} placeholder={t('label.name')}

View File

@ -11,7 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
.team-user-select-dropdown { .ant-card.team-user-select-dropdown {
padding: 8px; padding: 8px;
.ant-card-body { .ant-card-body {
@ -38,7 +38,7 @@
} }
} }
.select-trigger { .ant-btn.select-trigger {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;

View File

@ -21,8 +21,8 @@ import ResizablePanels from '../../../components/common/ResizablePanels/Resizabl
import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor'; import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor';
import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component'; import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ERROR_MESSAGE } from '../../../constants/constants'; import { ERROR_MESSAGE } from '../../../constants/constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants'; import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import { import {
CreatePolicy, CreatePolicy,
Effect, Effect,
@ -131,22 +131,7 @@ const AddPolicyPage = () => {
<Form.Item <Form.Item
label={`${t('label.name')}:`} label={`${t('label.name')}:`}
name="name" name="name"
rules={[ rules={NAME_FIELD_RULES}>
{
required: true,
max: 128,
min: 1,
message: `${t('message.entity-size-in-between', {
entity: `${t('label.name')}`,
max: '128',
min: '1',
})}`,
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
<Input <Input
data-testid="policy-name" data-testid="policy-name"
placeholder={t('label.policy-name')} placeholder={t('label.policy-name')}

View File

@ -19,7 +19,7 @@ import { capitalize, startCase, uniq, uniqBy } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react'; import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor'; import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants'; import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { import {
Effect, Effect,
Operation, Operation,
@ -203,22 +203,7 @@ const RuleForm: FC<RuleFormProps> = ({ ruleData, setRuleData }) => {
<Form.Item <Form.Item
label={`${t('label.rule-name')}:`} label={`${t('label.rule-name')}:`}
name="ruleName" name="ruleName"
rules={[ rules={NAME_FIELD_RULES}>
{
required: true,
max: 128,
min: 1,
message: `${t('message.entity-size-in-between', {
entity: `${t('label.name')}`,
max: '128',
min: '1',
})}`,
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
<Input <Input
data-testid="rule-name" data-testid="rule-name"
placeholder={t('label.rule-name')} placeholder={t('label.rule-name')}

View File

@ -21,8 +21,8 @@ import ResizablePanels from '../../../components/common/ResizablePanels/Resizabl
import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor'; import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor';
import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component'; import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ERROR_MESSAGE } from '../../../constants/constants'; import { ERROR_MESSAGE } from '../../../constants/constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants'; import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import { Policy } from '../../../generated/entity/policies/policy'; import { Policy } from '../../../generated/entity/policies/policy';
import { addRole, getPolicies } from '../../../rest/rolesAPIV1'; import { addRole, getPolicies } from '../../../rest/rolesAPIV1';
import { getIsErrorMatch } from '../../../utils/CommonUtils'; import { getIsErrorMatch } from '../../../utils/CommonUtils';
@ -132,22 +132,7 @@ const AddRolePage = () => {
<Form.Item <Form.Item
label={`${t('label.name')}:`} label={`${t('label.name')}:`}
name="name" name="name"
rules={[ rules={NAME_FIELD_RULES}>
{
required: true,
max: 128,
min: 1,
message: `${t('message.entity-size-in-between', {
entity: `${t('label.name')}`,
max: '128',
min: '1',
})}`,
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
<Input <Input
data-testid="name" data-testid="name"
placeholder={t('label.role-name')} placeholder={t('label.role-name')}

View File

@ -25,6 +25,7 @@ import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBre
import ExploreSearchCard from '../../../components/ExploreV1/ExploreSearchCard/ExploreSearchCard'; import ExploreSearchCard from '../../../components/ExploreV1/ExploreSearchCard/ExploreSearchCard';
import { SearchedDataProps } from '../../../components/SearchedData/SearchedData.interface'; import { SearchedDataProps } from '../../../components/SearchedData/SearchedData.interface';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { VALIDATION_MESSAGES } from '../../../constants/constants';
import { EntityField } from '../../../constants/Feeds.constants'; import { EntityField } from '../../../constants/Feeds.constants';
import { TASK_SANITIZE_VALUE_REGEX } from '../../../constants/regex.constants'; import { TASK_SANITIZE_VALUE_REGEX } from '../../../constants/regex.constants';
import { EntityTabs, EntityType } from '../../../enums/entity.enum'; import { EntityTabs, EntityType } from '../../../enums/entity.enum';
@ -108,9 +109,9 @@ const UpdateDescription = () => {
const getDescription = () => { const getDescription = () => {
if (!isEmpty(columnObject) && !isUndefined(columnObject)) { if (!isEmpty(columnObject) && !isUndefined(columnObject)) {
return columnObject.description || ''; return columnObject.description ?? '';
} else { } else {
return entityData.description || ''; return entityData.description ?? '';
} }
}; };
@ -225,6 +226,7 @@ const UpdateDescription = () => {
data-testid="form-container" data-testid="form-container"
form={form} form={form}
layout="vertical" layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={onCreateTask}> onFinish={onCreateTask}>
<Form.Item <Form.Item
data-testid="title" data-testid="title"
@ -241,14 +243,7 @@ const UpdateDescription = () => {
data-testid="assignees" data-testid="assignees"
label={`${t('label.assignee-plural')}:`} label={`${t('label.assignee-plural')}:`}
name="assignees" name="assignees"
rules={[ rules={[{ required: true }]}>
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.assignee-plural'),
}),
},
]}>
<Assignees <Assignees
options={options} options={options}
value={assignees} value={assignees}
@ -262,14 +257,7 @@ const UpdateDescription = () => {
data-testid="description-tabs" data-testid="description-tabs"
label={`${t('label.description')}:`} label={`${t('label.description')}:`}
name="description" name="description"
rules={[ rules={[{ required: true }]}>
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.description'),
}),
},
]}>
<DescriptionTabs <DescriptionTabs
suggestion={currentDescription} suggestion={currentDescription}
value={currentDescription} value={currentDescription}

View File

@ -19,7 +19,7 @@ import { useTranslation } from 'react-i18next';
import RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor'; import RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor';
import { EditorContentRef } from '../../components/common/RichTextEditor/RichTextEditor.interface'; import { EditorContentRef } from '../../components/common/RichTextEditor/RichTextEditor.interface';
import { VALIDATION_MESSAGES } from '../../constants/constants'; import { VALIDATION_MESSAGES } from '../../constants/constants';
import { ENTITY_NAME_REGEX } from '../../constants/regex.constants'; import { NAME_FIELD_RULES } from '../../constants/Form.constants';
import { Team, TeamType } from '../../generated/entity/teams/team'; import { Team, TeamType } from '../../generated/entity/teams/team';
import { import {
FieldProp, FieldProp,
@ -120,17 +120,7 @@ const AddTeamForm: React.FC<AddTeamFormType> = ({
label={t('label.name')} label={t('label.name')}
name="name" name="name"
rules={[ rules={[
{ ...NAME_FIELD_RULES,
required: true,
type: 'string',
min: 1,
max: 128,
whitespace: true,
},
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
{ {
validator: (_, value) => { validator: (_, value) => {
if ( if (

View File

@ -21,6 +21,7 @@ import {
Switch, Switch,
TooltipProps, TooltipProps,
} from 'antd'; } from 'antd';
import { RuleObject } from 'antd/lib/form';
import { TooltipPlacement } from 'antd/lib/tooltip'; import { TooltipPlacement } from 'antd/lib/tooltip';
import classNames from 'classnames'; import classNames from 'classnames';
import { compact, startCase } from 'lodash'; import { compact, startCase } from 'lodash';
@ -64,7 +65,12 @@ export const getField = (field: FieldProp) => {
let internalFormItemProps: FormItemProps = {}; let internalFormItemProps: FormItemProps = {};
let fieldElement: ReactNode = null; let fieldElement: ReactNode = null;
let fieldRules = [...rules]; let fieldRules = [...rules];
if (required) { // Check if required rule is already present to avoid rule duplication
const isRequiredRulePresent = rules.some(
(rule) => (rule as RuleObject).required ?? false
);
if (required && !isRequiredRulePresent) {
fieldRules = [ fieldRules = [
...fieldRules, ...fieldRules,
{ {