feat(data-quality): migrate test case & bundle suite forms to react-hook-form#29784
feat(data-quality): migrate test case & bundle suite forms to react-hook-form#29784ShaileshParmar11 wants to merge 34 commits into
Conversation
…mponents RHF Port of the reviewed dq-dashboard-form-migration branch onto main (single squashed application; original bases were squash-merged): - New RHF form stack: TestCaseFormBody/Drawer, BundleSuiteFormBody/Drawer, ParameterFields, TableDiffFields, TestCaseSchedulerSection, transforms, and ParameterFieldsUtils (param name sanitize/restore, payload normalization, select-item unwrapping). - Hosts support variant="classic" (right drawer) and variant="ai" (centered modal via new AiFormModal + floating form hint). - All OSS consumers migrated to the controlled-open hosts; legacy antd TestCaseFormV1 and BundleSuiteForm removed. - core-components: FieldPropsMap gains isDisabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The classic drawer hosts passed no width, so SlideoutMenu fell back to its narrow default and the side doc panel collapsed into a vertical sliver. Match the legacy antd drawer sizing (was width=80%). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…form The migrated body rendered flat fields, so the classic drawer lost the old form's look: TestCaseFormV1.less keys everything on .test-case-form-v1, which the new markup never carried. - Body root now carries test-case-form-v1/drawer-mode so the kept stylesheet applies; sections wrapped as form-card-section cards (select-table, test-type, test-details, scheduler with its title). - Description renders the RichTextEditor block editor again instead of the core TextArea fallback. - Custom Query action right-aligned in the test-type card header. - Plain-div card sections get the padding antd Card used to provide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e drawer Scoped to .drawer-content-wrapper so the AI modal keeps design-system defaults: medium gray labels, ~32px controls with 4px radius, no leading search icon, and the Custom Query action overlaid on the Select Test Type label row like the legacy absolute-positioned header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The SlideoutMenu overlay carried no z-index, letting the page table's sticky Actions column paint through the drawer; the hosts now tag the overlay (test-case-form-drawer / bundle-suite-form-drawer) and it gets z-index 40 — above page chrome, below the select popovers. - Classic controls pinned to 32px to match the legacy antd sizing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ase form The core getField TAG_SUGGESTION/GLOSSARY_TAG_SUGGESTION render a plain Autocomplete with caller-supplied options, so both fields were empty. Reuse the self-fetching common TagSuggestion (core Autocomplete based) wrapped in RHF FormField, and teach it a tagType prop so the glossary variant searches the glossaryTerm index and stamps source=Glossary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… results Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tion fields Clicking one TagSuggestion while another's popover is open cancels the menu the click just opened (the closing popover's teardown races the open). On focus, re-open via the trigger's ArrowDown handling, with a follow-up check after the teardown window since the cancel can land late. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fields Only a few fields reported focus to the doc panel. Replace the per-field wiring with one focus-capture handler on the form body that resolves the nearest root/<fieldName> id, matching the legacy antd form's form-level onFocus behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Show helper descriptions as label (?) tooltips instead of inline alert banners for test type, parameters, and compute row count - Show only the selected test name in the test type trigger; the description stays in the dropdown options - Follow the selected test type in the doc panel until it is cleared - Match the legacy regular font weight on the Custom Query toggle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… icons Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the combobox re-open guard from TagSuggestion into a shared ensureComboboxMenuOpen util and apply it to the table async select, whose just-opened menu could be cancelled by the focus-driven doc panel re-render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ssion label Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Bind the custom query SQL editor to the form value; typed SQL now reaches params.sqlExpression and required validation can pass - Render generic inputs for the remaining tableDiff parameters (where, threshold, caseSensitiveColumns) - Render testCaseClassBase.createFormAdditionalFields via the core getField (contract migrated to core FieldProp) so distribution overrides like the dynamic assertion switch appear again - Fall back to a text input for parameter data types without dedicated handling instead of rendering nothing - Seed preselected test cases into the bundle suite form value so the create-from-selection flow can submit Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Validate table edit permission on selection when the user lacks global test case create rights, gating pipeline creation like the legacy form - Funnel every drawer dismissal (cancel, X, Escape, programmatic) through one onClose so the parent is notified exactly once and the form resets; backdrop is inert like the legacy maskClosable drawer - Fall back to the generated ingestion name when the pipeline name is cleared to an empty string - Clear the selected column when switching back to table level - Restore the rich text editor for the bundle suite description - Restore the pipeline-deployed success toast for bundle suites - Double the table search page size (legacy infinite scroll needs core Autocomplete support; tracked as follow-up) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Report focused parameter fields to the doc panel via the selected test type id, like the legacy param wrapper - Show parameter descriptions as label tooltips on Array/Set lists - Scroll to the first invalid field on Enter-key submits too - Reset the form when the AI modal closes or submits - Restore the legacy bundle suite drawer width (736px), footer test ids (submit-button/cancel-button, create-btn), and the optional pipeline description under the scheduler toggle Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Re-seed bundle suite form values on every open: the always-mounted drawer host froze useForm defaults before DataQualityTab's bulk selection provided initialValues, so create-from-selection could never submit; covered by a production-topology test - Validate table permission at selection time (legacy antd change-time feedback) instead of first at submit - Scroll to the first invalid field on bundle Enter-key and AI modal submits - Drop the duplicate root/<testType> DOM id; the parameter wrapper reports doc panel focus via a handler like the legacy onClick wrapper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The doc-panel focus wrapper made the parameters a single card child, so the card's child spacing no longer applied between them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e scroll Attaches a scroll listener to the options popover from the list box ref and calls onLoadMore when scrolled near the end, so async selects can page in more options like the legacy antd AsyncSelect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Table search pages in more results on scroll (309 tables reachable again) via the new core Autocomplete onLoadMore - Doc panel switches to the selected table's entity summary on selection (root/selected-entity), like the legacy option onclick - Create button shows the legacy isCheckingPermissions loading state - Clearing the table re-validates, clearing a stale permission error - tableDiff parameters render in parameterDefinition order - Test case drawer backdrop is dismissable again (legacy antd default; the bundle drawer keeps its legacy mask lock) - Bundle form re-seeds when the seed content changes while open, closing the late-initialValues edge without wiping user edits on parent re-renders Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Permission-check spinner shows only on the Create button via a new additive submitLoading option, not as a full-body loader overlay - loadMoreTables uses a ref-based in-flight latch instead of relying on render-flush timing - Doc panel reverts to the generic table section when the table selection is cleared Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
react-hook-form ignores onChange(undefined), but ScheduleIntervalV1 signals On Demand with undefined — so the cron value never cleared and the value-sync effect snapped the card back to Schedule. Store an empty string in the form as the on-demand sentinel, convert to undefined at the component and pipeline payload boundaries, and cover the RHF wiring with a regression test using the real selection cards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Select options accept an optional testId rendered as data-testid (core), and test type options carry the definition FQN like the legacy antd options - Parameter fields keep the legacy antd form-item ids (testCaseFormV1_params_*) as stable selectors, including array rows and tableDiff fields - The form body carries the test-case-form-v1 test id again Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ites - Fetch tables on drawer mount like the legacy AsyncSelect, so options are ready before first focus - Validate on change (RHF mode onChange), restoring antd's while-typing name and field validation feedback - Enum parameters with optionValues render as a single select even when typed ARRAY (legacy precedence); the custom SQL strategy field was a free-text array row list - tableDiff cross-list column exclusion compares select item ids, not item objects, so key columns disable in Use Columns again - Restore the legacy scheduler-card test id Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
antd-era interaction patterns rewritten for the react-aria form: dropdown popovers to role listbox/option picks, filter-typing to click-to-open, heading-click dismissals to Escape, array add rows via add-<param> testids, tag options to tag-option-<fqn>, wrapper testid fills retargeted to inner inputs, validation-help assertions to text-based checks. Edit-modal (unmigrated antd) selectors untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The AI-mode modal's Cancel/Create buttons had no test ids, so the shared submit helpers (and the AskCollate CTA flows) could not drive the AI variant. Add configurable submitTestId/cancelTestId defaulting to create-btn/cancel-btn to match the classic test case drawer; the bundle suite drawer passes submit-button/cancel-button to match its own. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FieldProp already exposes disabled; the added isDisabled duplicated it. Remove isDisabled from the public type and map the single disabled prop to react-aria's isDisabled convention inside the Autocomplete and Select renderers, so one prop disables both the react-aria selects and the legacy field components. DQ form fields updated to pass disabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rework the form to need no changes to the shared component library: - Revert the Autocomplete onLoadMore, SelectItemType.testId, and the disabled->isDisabled renderer mapping in core-components - Table search is a single paged fetch again (search to narrow) instead of infinite scroll - Fields disable by passing react-aria's isDisabled through the existing prop spread (no new public FieldProp), so no duplicate of disabled - Test type options no longer carry a per-option test id Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test-type dropdown options no longer carry a per-option test id
(the core-components change was reverted). Select them by their visible
display name via getByRole('option').filter({ hasText }) instead, the
same pattern the table and column option picks already use. Display
names are the test definitions' displayName values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Paired Collate PR: open-metadata/openmetadata-collate#4874 |
| @@ -134,11 +144,14 @@ const TagSuggestion: FC<TagSuggestionProps> = ({ | |||
|
|
|||
| const handleItemInserted = useCallback( | |||
There was a problem hiding this comment.
💡 Bug: TagSuggestion re-inserts already-selected tag as duplicate
In handleItemInserted, when the inserted key already exists in value, existingTag is found but the handler still calls onChange?.([...value, newTag]), appending the existing tag again. If react-aria's Autocomplete ever fires onItemInserted for an already-selected item (e.g. rapid re-selection), the tag array will contain duplicates, which then flow into normalizeTagLabels and the created test case payload. Consider guarding: if (existingTag) { return; } before appending, or dedupe by tagFQN before calling onChange.
Skip insertion when the tag is already selected.:
const handleItemInserted = useCallback(
(key: string | number) => {
if (String(key) === NO_DATA_OPTION_ID) {
return;
}
if (value.some((tag) => tag.tagFQN === String(key))) {
return; // already selected; avoid duplicate entry
}
const tagData = tagDataMap.current.get(String(key));
const newTag: EntityTags = {
tagFQN: String(key),
source: tagType,
name: tagData?.name,
displayName: tagData?.displayName,
description: tagData?.description,
style: tagData?.style,
};
onChange?.([...value, newTag]);
searchDebounced.cancel();
fetchOptions('');
},
[value, onChange, searchDebounced, tagType]
);
- Apply fix
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎
| }) => { | ||
| const { t } = useTranslation(); | ||
| const [options, setOptions] = useState<TagSelectItem[]>([]); | ||
| const tagDataMap = useRef<Map<string, TagLabel>>(new Map()); |
There was a problem hiding this comment.
💡 Quality: tagDataMap is an unbounded Map cache without a size cap
The repo caching guideline requires caches to have explicit size caps (use lru-cache in TS, not a bare Map). tagDataMap in TagSuggestion is a useRef<Map<string, TagLabel>> that accumulates an entry for every option returned by every search/glossary fetch for the lifetime of the component and is never pruned. On a long-lived form with many searches this grows unbounded. Note TestCaseFormBody's tablesCache correctly caps at TABLES_CACHE_MAX_SIZE; apply the same bounded pattern (or an lru-cache) to tagDataMap.
Was this helpful? React with 👍 / 👎
| const seedSignature = JSON.stringify([ | ||
| initialValues?.name ?? '', | ||
| initialValues?.description ?? '', | ||
| (initialValues?.testCases ?? []).map((tc) => tc.id), | ||
| ]); | ||
|
|
||
| useEffect(() => { | ||
| if (open) { | ||
| form.reset(computeDefaultValues()); | ||
| } | ||
| }, [open, seedSignature]); |
There was a problem hiding this comment.
💡 Quality: Bundle suite re-seed effect omits deps and relies on JSON.stringify signature
The re-seed effect in BundleSuiteFormDrawer intentionally depends only on [open, seedSignature] and excludes form/computeDefaultValues, and seedSignature is a JSON.stringify of name/description/testCase ids recomputed on every render. This works but is fragile: (1) it will trip react-hooks/exhaustive-deps and any future edit to computeDefaultValues closure that isn't reflected in seedSignature will silently use stale seed data; (2) JSON.stringify on every render is wasted work. Consider memoizing seedSignature with useMemo and adding an eslint-disable with an explanatory comment (already partially present) so the intent is enforced rather than incidental.
Was this helpful? React with 👍 / 👎
Code Review 👍 Approved with suggestions 0 resolved / 3 findingsMigrates Data Quality forms to react-hook-form while maintaining parity with legacy behaviors and selectors. Address the duplicate TagSuggestion insertion, implement an LRU cache for tagDataMap, and refine the Bundle suite re-seed effect dependencies. 💡 Bug: TagSuggestion re-inserts already-selected tag as duplicate📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:145-159 In Skip insertion when the tag is already selected.💡 Quality: tagDataMap is an unbounded Map cache without a size cap📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:64 📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:85-87 📄 openmetadata-ui/src/main/resources/ui/src/components/common/TagSuggestion/TagSuggestion.tsx:113-115 The repo caching guideline requires caches to have explicit size caps (use 💡 Quality: Bundle suite re-seed effect omits deps and relies on JSON.stringify signatureThe re-seed effect in BundleSuiteFormDrawer intentionally depends only on 🤖 Prompt for agentsOptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
What
Migrates the Data Quality Add Test Case and Add Bundle Suite forms from Ant Design to the react-hook-form + react-aria
core-componentsstack (getField/FieldProp/HookForm).core-componentsis net-unchangedTwo intermediate commits (
onLoadMore, anisDisabledfield prop) touch the shared library, but they are fully reverted by the end of the branch —openmetadata-ui-core-componentsis byte-identical tomain(0 diff). The form meets all its needs through existing primitives:isDisabledthrough the existing{...rest}spread — no new public prop.Testing
getByRole('option').filter({ hasText }). Validated live:AddTestCaseNewFlow(table + column + pipeline + permission),TableLevelTestscustom-SQL.Notes
mainand can be rebased before marking ready.🤖 Generated with Claude Code