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

View File

@ -14,7 +14,7 @@
import {
DELETE_TERM,
INVALID_NAMES,
NAME_MAX_LENGTH_VALIDATION_ERROR,
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128,
NAME_VALIDATION_ERROR,
} from '../constants/constants';
import { SidebarItem } from '../constants/Entity.interface';
@ -40,9 +40,10 @@ export const validateForm = () => {
.scrollIntoView()
.should('be.visible')
.type(INVALID_NAMES.MAX_LENGTH);
cy.get('#name_help')
.should('be.visible')
.contains(NAME_MAX_LENGTH_VALIDATION_ERROR);
cy.get('#name_help').should(
'contain',
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128
);
// with special char validation
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,
verifyResponseStatusCode,
} from '../common';
import { validateFormNameFieldInput } from './Form';
const TEAM_TYPES = ['Department', 'Division', 'Group'];
@ -179,7 +180,12 @@ export const addTeam = (
verifyResponseStatusCode('@addTeam', 200);
// 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);

View File

@ -621,9 +621,8 @@ export const NAME_VALIDATION_ERROR =
export const NAME_MIN_MAX_LENGTH_VALIDATION_ERROR =
'Name size must be between 2 and 64';
export const NAME_MAX_LENGTH_VALIDATION_ERROR =
'Name can be a maximum of 128 characters';
export const NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128 =
'Name size must be between 1 and 128';
export const DOMAIN_1 = {
name: 'Cypress%Domain',
@ -836,3 +835,5 @@ export const JWT_EXPIRY_TIME_MAP = {
'1 hour': 3600,
'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 {
INVALID_NAMES,
NAME_MAX_LENGTH_VALIDATION_ERROR,
NAME_MIN_MAX_LENGTH_VALIDATION_ERROR_1_128,
NAME_VALIDATION_ERROR,
uuid,
} from '../../constants/constants';
@ -53,7 +53,10 @@ const validateForm = (isColumnMetric = false) => {
// max length validation
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
cy.get('#name')

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ import {
uuid,
verifyResponseStatusCode,
} from '../../common/common';
import { validateFormNameFieldInput } from '../../common/Utils/Form';
import { BASE_URL } from '../../constants/constants';
import { GlobalSettingOptions } from '../../constants/settings.constant';
@ -58,7 +59,12 @@ const newRuledescription = `This is ${newRuleName} description`;
const updatedRuleName = `New-Rule-test-${uuid()}-updated`;
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
cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror')
.eq(descriptionIndex)
@ -127,6 +133,12 @@ describe('Policy page should work properly', { tags: 'Settings' }, () => {
// Enter policy name
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
cy.get(descriptionBox).eq(0).type(description);

View File

@ -123,7 +123,17 @@ const PopoverContent: FC<PopoverContentProps> = ({
onFinish={handleEmbedImage}>
<Row gutter={[8, 8]}>
<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
autoFocus
data-testid="embed-input"

View File

@ -15,7 +15,8 @@ import QueryString from 'qs';
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
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 { CustomMetric } from '../../../generated/entity/data/table';
import { getEntityName } from '../../../utils/EntityUtils';
@ -76,29 +77,13 @@ const CustomMetricForm = ({
data-testid="custom-metric-form"
form={form}
layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={onFinish}>
<Form.Item
label={t('label.name')}
name="name"
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',
})}`,
},
...NAME_FIELD_RULES,
{
validator: (_, value) => {
if (metricNames.includes(value) && !isEditMode) {
@ -126,9 +111,6 @@ const CustomMetricForm = ({
rules={[
{
required: true,
message: t('label.field-required', {
field: t('label.column'),
}),
},
]}>
<Select
@ -152,9 +134,6 @@ const CustomMetricForm = ({
rules={[
{
required: true,
message: t('label.field-required', {
field: t('label.sql-uppercase-query'),
}),
},
]}
trigger="onChange">

View File

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

View File

@ -19,7 +19,7 @@ import {
PAGE_SIZE_MEDIUM,
VALIDATION_MESSAGES,
} 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 { DataQualityPageTabs } from '../../../../pages/DataQuality/DataQualityPage.interface';
import { getListTestSuites } from '../../../../rest/testAPI';
@ -78,22 +78,7 @@ const AddTestSuiteForm: React.FC<AddTestSuiteFormProps> = ({
label={t('label.name')}
name="name"
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',
})}`,
},
...NAME_FIELD_RULES,
{
validator: (_, 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 { UserTag } from '../../../components/common/UserTag/UserTag.component';
import { UserTagSize } from '../../../components/common/UserTag/UserTag.interface';
import {
ENTITY_NAME_REGEX,
HEX_COLOR_CODE_REGEX,
} from '../../../constants/regex.constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { HEX_COLOR_CODE_REGEX } from '../../../constants/regex.constants';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface';
import { CreateDataProduct } from '../../../generated/api/domains/createDataProduct';
@ -72,20 +70,7 @@ const AddDomainForm = ({
props: {
'data-testid': 'name',
},
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',
})}`,
},
],
rules: NAME_FIELD_RULES,
},
{
name: 'displayName',

View File

@ -16,7 +16,6 @@ import { Button, Form, Space, Typography } from 'antd';
import { FormProps, useForm } from 'antd/lib/form/Form';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import {
CreateGlossary,
EntityReference,
@ -29,6 +28,7 @@ import {
import { getEntityName } from '../../../utils/EntityUtils';
import { generateFormFields, getField } from '../../../utils/formUtils';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { EntityType } from '../../../enums/entity.enum';
import { useApplicationStore } from '../../../hooks/useApplicationStore';
import ResizablePanels from '../../common/ResizablePanels/ResizablePanels';
@ -104,20 +104,7 @@ const AddGlossary = ({
props: {
'data-testid': 'name',
},
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',
})}`,
},
],
rules: NAME_FIELD_RULES,
},
{
name: 'displayName',

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import { Button, Form, FormProps, Select, Space } from 'antd';
import { isEmpty } from 'lodash';
import React, { FC, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { VALIDATION_MESSAGES } from '../../../../constants/constants';
import {
PersonalAccessToken,
TokenType,
@ -85,67 +86,62 @@ const AuthMechanismForm: FC<Props> = ({
}, [isBot, authenticationMechanism]);
return (
<>
<Form
id="update-auth-mechanism-form"
initialValues={{ authType, tokenExpiry }}
layout="vertical"
onFinish={handleSave}>
<Form.Item label={t('label.auth-mechanism')} name="authType">
<Select
disabled
className="w-full"
data-testid="auth-mechanism"
placeholder={t('label.select-field', {
field: t('label.auth-mechanism'),
})}>
<Option key={authOptions.value}>{authOptions.label}</Option>
</Select>
</Form.Item>
<Form
id="update-auth-mechanism-form"
initialValues={{ authType, tokenExpiry }}
layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSave}>
<Form.Item label={t('label.auth-mechanism')} name="authType">
<Select
disabled
className="w-full"
data-testid="auth-mechanism"
placeholder={t('label.select-field', {
field: t('label.auth-mechanism'),
})}>
<Option key={authOptions.value}>{authOptions.label}</Option>
</Select>
</Form.Item>
<Form.Item
label={t('label.token-expiration')}
name="tokenExpiry"
rules={[
{
required: true,
},
]}>
<Select
className="w-full"
data-testid="token-expiry"
placeholder={t('message.select-token-expiration')}>
{isBot
? getJWTTokenExpiryOptions().map((option) => (
<Option key={option.value}>{option.label}</Option>
))
: getJWTTokenExpiryOptions()
.filter((option) => option.value !== 'Unlimited')
.map((filteredOption) => (
<Option key={filteredOption.value}>
{filteredOption.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t('label.token-expiration')}
name="tokenExpiry"
rules={[{ required: true }]}>
<Select
className="w-full"
data-testid="token-expiry"
placeholder={t('message.select-token-expiration')}>
{isBot
? getJWTTokenExpiryOptions().map((option) => (
<Option key={option.value}>{option.label}</Option>
))
: getJWTTokenExpiryOptions()
.filter((option) => option.value !== 'Unlimited')
.map((filteredOption) => (
<Option key={filteredOption.value}>
{filteredOption.label}
</Option>
))}
</Select>
</Form.Item>
<Space className="w-full justify-end" size={4}>
{!isEmpty(authenticationMechanism) && (
<Button data-testid="cancel-edit" type="link" onClick={onCancel}>
{t('label.cancel')}
</Button>
)}
<Button
data-testid="save-edit"
form="update-auth-mechanism-form"
htmlType="submit"
loading={isUpdating}
type="primary">
{t('label.generate')}
<Space className="w-full justify-end" size={4}>
{!isEmpty(authenticationMechanism) && (
<Button data-testid="cancel-edit" type="link" onClick={onCancel}>
{t('label.cancel')}
</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={[
{
required: true,
message: t('label.field-required', {
field: t('label.cron'),
}),
},
{
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 RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor';
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 { ENTITY_NAME_REGEX } from '../../constants/regex.constants';
import { CreateEventSubscription } from '../../generated/events/api/createEventSubscription';
import {
AlertType,
@ -212,6 +212,7 @@ const AddNotificationPage = () => {
...alert,
resources: alert?.filteringRules?.resources,
}}
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSave}>
{loadingCount > 0 ? (
<Skeleton title paragraph={{ rows: 8 }} />
@ -222,13 +223,7 @@ const AddNotificationPage = () => {
label={t('label.name')}
labelCol={{ span: 24 }}
name="name"
rules={[
{ required: true },
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
rules={NAME_FIELD_RULES}>
<Input
disabled={isEditMode}
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 RichTextEditor from '../../components/common/RichTextEditor/RichTextEditor';
import TitleBreadcrumb from '../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ROUTES } from '../../constants/constants';
import { ENTITY_NAME_REGEX } from '../../constants/regex.constants';
import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants';
import { NAME_FIELD_RULES } from '../../constants/Form.constants';
import { CreateEventSubscription } from '../../generated/events/api/createEventSubscription';
import {
AlertType,
@ -217,6 +217,7 @@ function AddObservabilityPage() {
...alert,
resources: alert?.filteringRules?.resources,
}}
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSave}>
<Row gutter={[20, 20]}>
<Col span={24}>
@ -224,13 +225,7 @@ function AddObservabilityPage() {
label={t('label.name')}
labelCol={{ span: 24 }}
name="name"
rules={[
{ required: true },
{
pattern: ENTITY_NAME_REGEX,
message: t('message.entity-name-validation'),
},
]}>
rules={NAME_FIELD_RULES}>
<Input
disabled={isEditMode}
placeholder={t('label.name')}

View File

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

View File

@ -21,8 +21,8 @@ import ResizablePanels from '../../../components/common/ResizablePanels/Resizabl
import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor';
import TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ERROR_MESSAGE } from '../../../constants/constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import {
CreatePolicy,
Effect,
@ -131,22 +131,7 @@ const AddPolicyPage = () => {
<Form.Item
label={`${t('label.name')}:`}
name="name"
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'),
},
]}>
rules={NAME_FIELD_RULES}>
<Input
data-testid="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 { useTranslation } from 'react-i18next';
import RichTextEditor from '../../../components/common/RichTextEditor/RichTextEditor';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import {
Effect,
Operation,
@ -203,22 +203,7 @@ const RuleForm: FC<RuleFormProps> = ({ ruleData, setRuleData }) => {
<Form.Item
label={`${t('label.rule-name')}:`}
name="ruleName"
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'),
},
]}>
rules={NAME_FIELD_RULES}>
<Input
data-testid="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 TitleBreadcrumb from '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component';
import { ERROR_MESSAGE } from '../../../constants/constants';
import { NAME_FIELD_RULES } from '../../../constants/Form.constants';
import { GlobalSettingOptions } from '../../../constants/GlobalSettings.constants';
import { ENTITY_NAME_REGEX } from '../../../constants/regex.constants';
import { Policy } from '../../../generated/entity/policies/policy';
import { addRole, getPolicies } from '../../../rest/rolesAPIV1';
import { getIsErrorMatch } from '../../../utils/CommonUtils';
@ -132,22 +132,7 @@ const AddRolePage = () => {
<Form.Item
label={`${t('label.name')}:`}
name="name"
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'),
},
]}>
rules={NAME_FIELD_RULES}>
<Input
data-testid="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 { SearchedDataProps } from '../../../components/SearchedData/SearchedData.interface';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { VALIDATION_MESSAGES } from '../../../constants/constants';
import { EntityField } from '../../../constants/Feeds.constants';
import { TASK_SANITIZE_VALUE_REGEX } from '../../../constants/regex.constants';
import { EntityTabs, EntityType } from '../../../enums/entity.enum';
@ -108,9 +109,9 @@ const UpdateDescription = () => {
const getDescription = () => {
if (!isEmpty(columnObject) && !isUndefined(columnObject)) {
return columnObject.description || '';
return columnObject.description ?? '';
} else {
return entityData.description || '';
return entityData.description ?? '';
}
};
@ -225,6 +226,7 @@ const UpdateDescription = () => {
data-testid="form-container"
form={form}
layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={onCreateTask}>
<Form.Item
data-testid="title"
@ -241,14 +243,7 @@ const UpdateDescription = () => {
data-testid="assignees"
label={`${t('label.assignee-plural')}:`}
name="assignees"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.assignee-plural'),
}),
},
]}>
rules={[{ required: true }]}>
<Assignees
options={options}
value={assignees}
@ -262,14 +257,7 @@ const UpdateDescription = () => {
data-testid="description-tabs"
label={`${t('label.description')}:`}
name="description"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.description'),
}),
},
]}>
rules={[{ required: true }]}>
<DescriptionTabs
suggestion={currentDescription}
value={currentDescription}

View File

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

View File

@ -21,6 +21,7 @@ import {
Switch,
TooltipProps,
} from 'antd';
import { RuleObject } from 'antd/lib/form';
import { TooltipPlacement } from 'antd/lib/tooltip';
import classNames from 'classnames';
import { compact, startCase } from 'lodash';
@ -64,7 +65,12 @@ export const getField = (field: FieldProp) => {
let internalFormItemProps: FormItemProps = {};
let fieldElement: ReactNode = null;
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,
{