From 8be8362f4d2edcd89690699d69e649aa2415a0ab Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 6 Jul 2026 21:22:27 +0530 Subject: [PATCH 1/4] refactor(entity-rename): replace antd components with core-components in EntityNameModal - Replace antd Form, Modal, Button, Typography with @openmetadata/ui-core-components equivalents (Dialog, ModalOverlay, Modal, Button, Typography) - Use react-hook-form (Controller/useForm) instead of antd Form context - Add SanitizedField helper wrapping InputBase in AriaTextField so onChange receives a string and id flows through to the DOM , preserving Playwright locators (#name, #displayName, #name_help, [role="dialog"]) - Update EntityNameModal.interface.ts: remove antd Rule import, add EntityNameValidationRule object type - Update ManageButton.tsx: cast DISPLAY_NAME_FIELD_RULES as EntityNameValidationRule[] - Update SchemaTable.component.tsx: replace antd Form context for constraint with local editConstraint state; cast updateColumnDetails arg as Partial - Update entityPanel.ts: replace .ant-modal selector with [role="dialog"] Co-Authored-By: Claude Sonnet 4.6 --- .../ui/playwright/utils/entityPanel.ts | 2 +- .../SchemaTable/SchemaTable.component.tsx | 33 ++- .../EntityNameModal.component.tsx | 273 +++++++++++++----- .../EntityNameModal.interface.ts | 13 +- .../ManageButton/ManageButton.tsx | 13 +- 5 files changed, 238 insertions(+), 96 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts index a0657ce506af..8052248f5fb5 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entityPanel.ts @@ -610,7 +610,7 @@ export const editDisplayNameFromPanel = async ( await editButton.waitFor({ state: 'visible' }); await editButton.click(); - const modal = page.locator('.ant-modal'); + const modal = page.locator('[role="dialog"]'); await modal.waitFor({ state: 'visible' }); const displayNameInput = modal.locator('#displayName'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx index aa71a41d93fd..f0418bbbd174 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/SchemaTable/SchemaTable.component.tsx @@ -15,7 +15,6 @@ import { Button, Col, Dropdown, - Form, Row, Select, TableProps, @@ -106,10 +105,7 @@ import Table from '../../common/Table/Table'; import TestCaseStatusSummaryIndicator from '../../common/TestCaseStatusSummaryIndicator/TestCaseStatusSummaryIndicator.component'; import { useGenericContext } from '../../Customization/GenericProvider/GenericContext'; import EntityNameModal from '../../Modals/EntityNameModal/EntityNameModal.component'; -import { - EntityName, - EntityNameWithAdditionFields, -} from '../../Modals/EntityNameModal/EntityNameModal.interface'; +import { EntityName } from '../../Modals/EntityNameModal/EntityNameModal.interface'; import { ColumnFilter } from '../ColumnFilter/ColumnFilter.component'; import TableDescription from '../TableDescription/TableDescription.component'; import TableTags from '../TableTags/TableTags.component'; @@ -169,6 +165,7 @@ const SchemaTable = () => { } = useFqn({ type: EntityType.TABLE }); const [editColumnDisplayName, setEditColumnDisplayName] = useState(); + const [editConstraint, setEditConstraint] = useState(); const { permissions: tablePermissions, @@ -599,10 +596,11 @@ const SchemaTable = () => { const handleEditDisplayNameClick = useCallback((record: Column) => { setEditColumnDisplayName(record); + setEditConstraint(record.constraint); }, []); const handleEditColumnData = async (data: EntityName) => { - const { displayName, constraint } = data as EntityNameWithAdditionFields; + const { displayName } = data; if ( !isUndefined(editColumnDisplayName) && editColumnDisplayName.fullyQualifiedName @@ -612,21 +610,23 @@ const SchemaTable = () => { editColumnDisplayName.fullyQualifiedName, { displayName: displayName, - ...(isEmpty(constraint) + ...(isEmpty(editConstraint) ? { removeConstraint: true, } - : { constraint }), - }, + : { constraint: editConstraint }), + } as Partial, 'displayName' ); } catch (error) { showErrorToast(error as AxiosError); } finally { setEditColumnDisplayName(undefined); + setEditConstraint(undefined); } } else { setEditColumnDisplayName(undefined); + setEditConstraint(undefined); } }; @@ -920,11 +920,12 @@ const SchemaTable = () => { ); const additionalFieldsInEntityNameModal = ( - +
+ . + * The AriaTextField wrapper div does NOT receive the id (deleted from DOMProps). + */ +const SanitizedField = ({ + id, + value, + onChange, + isDisabled, + isInvalid, + placeholder, +}: SanitizedFieldProps) => ( + onChange(getSanitizeContent(val))}> + + +); const EntityNameModal = ({ visible, @@ -31,84 +100,142 @@ const EntityNameModal = ({ displayNameValidationRules = [], }: EntityNameModalProps) => { const { t } = useTranslation(); - const [form] = Form.useForm(); const [isLoading, setIsLoading] = useState(false); - const handleSave: FormProps['onFinish'] = async (obj) => { + const { control, handleSubmit, reset } = useForm({ + defaultValues: { + name: entity.name, + displayName: entity.displayName ?? '', + }, + }); + + useEffect(() => { + if (visible) { + reset({ + name: entity.name, + displayName: entity.displayName ?? '', + }); + } + }, [visible, entity, reset]); + + const onSubmit = async (data: EntityName) => { setIsLoading(true); - await form.validateFields(); - // Error must be handled by the parent component - await onSave(obj); + await onSave(data as T); setIsLoading(false); }; - useEffect(() => { - form.setFieldsValue(entity); - }, [visible]); - return ( - - {t('label.cancel')} - , - , - ]} - maskClosable={false} - okText={t('label.save')} - open={visible} - title={ - - {title} - - } - wrapProps={{ - 'data-react-aria-top-layer': 'true', - }} - onCancel={onCancel}> -
- - - - - - + !isOpen && onCancel()}> + + + + + {title} + + + + +
+ + ( + <> + + {fieldState.error && ( + + {fieldState.error.message} + + )} + + )} + /> +
+ +
+ + ( + <> + + {fieldState.error && ( + + {fieldState.error.message} + + )} + + )} + /> +
- {additionalFields} - -
+ {additionalFields} + + + + + + + +
+ ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts index 38f05e9faeb3..374bdf683563 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts @@ -10,7 +10,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Rule } from 'antd/lib/form'; import { Constraint } from '../../../generated/entity/data/table'; export type EntityName = { name: string; displayName?: string; id?: string }; @@ -19,6 +18,14 @@ export type EntityNameWithAdditionFields = EntityName & { constraint: Constraint; }; +export type EntityNameValidationRule = { + min?: number; + max?: number; + pattern?: RegExp; + required?: boolean | string; + message?: string; +}; + export interface EntityNameModalProps< T extends { name: string; displayName?: string } > { @@ -28,7 +35,7 @@ export interface EntityNameModalProps< onSave: (obj: T) => void | Promise; entity: T; title: string; - nameValidationRules?: Rule[]; - displayNameValidationRules?: Rule[]; + nameValidationRules?: EntityNameValidationRule[]; + displayNameValidationRules?: EntityNameValidationRule[]; additionalFields?: React.ReactNode; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx index 9f917dd8108d..d8abf8e3efe8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx @@ -31,8 +31,11 @@ import { ANNOUNCEMENT_ENTITIES } from '../../../../utils/AnnouncementsUtils'; import entityUtilClassBase from '../../../../utils/EntityUtilClassBase'; import { showErrorToast } from '../../../../utils/ToastUtils'; import EntityNameModal from '../../../Modals/EntityNameModal/EntityNameModal.component'; -import { EntityName } from '../../../Modals/EntityNameModal/EntityNameModal.interface'; -import DeleteWidgetModal from '../../DeleteWidget/DeleteWidgetModal'; +import { + EntityName, + EntityNameValidationRule, +} from '../../../Modals/EntityNameModal/EntityNameModal.interface'; +import DeleteEntityModal from '../../DeleteWidget/DeleteEntityModal'; import { ManageButtonItemLabel } from '../../ManageButtonContentItem/ManageButtonContentItem.component'; import { ManageButtonProps } from './ManageButton.interface'; import './ManageButton.less'; @@ -305,7 +308,7 @@ const ManageButton: FC = ({ <> {items.length ? renderDropdownTrigger() : null} {isDelete && ( - = ({ {onEditDisplayName && isDisplayNameEditing && ( Date: Mon, 6 Jul 2026 21:52:25 +0530 Subject: [PATCH 2/4] fix(entity-rename): sort Controller props in EntityNameModal Move the rules prop after render on the name/displayName Controllers to satisfy the sort-props lint rule. Co-Authored-By: Claude Opus 4.8 --- .../EntityNameModal.component.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx index f8e90b7b86be..fe969171f65b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx @@ -152,16 +152,6 @@ const EntityNameModal = ({ ( <> ({ )} )} + rules={{ + required: `${t('label.field-required', { + field: t('label.name'), + })}`, + pattern: { + value: ENTITY_NAME_REGEX, + message: t('message.entity-name-validation'), + }, + validate: buildValidate(nameValidationRules), + }} />
@@ -195,9 +195,6 @@ const EntityNameModal = ({ ( <> ({ )} )} + rules={{ + validate: buildValidate(displayNameValidationRules), + }} /> From e511ad9ae0a77178531042f92b00a81628adcd0a Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Mon, 6 Jul 2026 21:55:19 +0530 Subject: [PATCH 3/4] fix(entity-rename): address gitar-bot review on EntityNameModal - buildValidate: skip min/max/pattern for empty non-required values, mirroring antd async-validator behavior (clearing displayName no longer produces a spurious min-length error) - useEffect reset: depend on entity.name/entity.displayName primitives instead of the entity object reference, so inline object literals passed by callers (e.g. ManageButton) don't wipe in-progress edits on every parent re-render Co-Authored-By: Claude Sonnet 4.6 --- .../EntityNameModal/EntityNameModal.component.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx index fe969171f65b..7041bda8c6b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.component.tsx @@ -35,6 +35,11 @@ const buildValidate = (value: string | undefined): string | true => { const v = value ?? ''; for (const rule of rules) { + // Skip length/pattern checks for empty non-required values, + // mirroring antd async-validator behavior. + if (v.length === 0 && !rule.required) { + continue; + } if (rule.min !== undefined && v.length < rule.min) { return rule.message ?? ''; } @@ -116,7 +121,11 @@ const EntityNameModal = ({ displayName: entity.displayName ?? '', }); } - }, [visible, entity, reset]); + // Depend on primitive values, not the entity object reference — + // inline objects (e.g. ManageButton's entity={{ name, displayName }}) + // are recreated every render, which would wipe in-progress edits. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visible, entity.name, entity.displayName, reset]); const onSubmit = async (data: EntityName) => { setIsLoading(true); From 53af925f25bbfe6c8c0f3cd9e169c5a7de6aa3eb Mon Sep 17 00:00:00 2001 From: anuj-kumary Date: Tue, 7 Jul 2026 00:34:21 +0530 Subject: [PATCH 4/4] fix(entity-rename): restore DeleteWidgetModal import in ManageButton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous commit accidentally renamed DeleteWidgetModal → DeleteEntityModal causing all test suites that import ManageButton to fail with "Cannot find module '../../DeleteWidget/DeleteEntityModal'". Co-Authored-By: Claude Sonnet 4.6 --- .../common/EntityPageInfos/ManageButton/ManageButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx index d8abf8e3efe8..0ce409ec4577 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/EntityPageInfos/ManageButton/ManageButton.tsx @@ -35,7 +35,7 @@ import { EntityName, EntityNameValidationRule, } from '../../../Modals/EntityNameModal/EntityNameModal.interface'; -import DeleteEntityModal from '../../DeleteWidget/DeleteEntityModal'; +import DeleteWidgetModal from '../../DeleteWidget/DeleteWidgetModal'; import { ManageButtonItemLabel } from '../../ManageButtonContentItem/ManageButtonContentItem.component'; import { ManageButtonProps } from './ManageButton.interface'; import './ManageButton.less'; @@ -308,7 +308,7 @@ const ManageButton: FC = ({ <> {items.length ? renderDropdownTrigger() : null} {isDelete && ( -