feat(query-builder): migrate QueryBuilderWidgetV1 from antd to core-components - #29849
feat(query-builder): migrate QueryBuilderWidgetV1 from antd to core-components#29849chirag-madlani wants to merge 118 commits into
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nput Replace @react-awesome-query-builder/antd widgets with core-components. Two new widgets using Input component: - OMTextWidget: string input values - OMNumberWidget: numeric input with type="number" All tests passing, TypeScript strict compilation verified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…support Implements Task 3 of the query builder migration from Ant Design to openmetadata-ui-core-components. Provides async-capable single-select widget wrapping core Select component. Includes handling for both static list values and async fetch callbacks, with proper TypeScript typing. - Converts listValues (array or object format) to SelectItemType[] - Supports async data loading via asyncFetch callback - Properly disables when readonly - Fully tested with 2 core test cases (render + disabled state) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ith async support Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…and ButtonGroup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m core-component widgets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate button renderers to core-components - AdvancedSearchClassBase: replace BasicConfig value with OMConfig from QueryBuilderOMConfig; BasicConfig is now type-only - AdvancedSearchUtils: renderAdvanceSearchButtons uses Button (core), X and Trash01 icons from @untitledui/icons; removes @ant-design/icons and antd Button imports - QueryBuilderUtils: renderQueryBuilderFilterButtons and renderJSONLogicQueryBuilderButtons use Button (core) and X/Plus from @untitledui/icons; removes antd and @ant-design/icons imports Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…and button renderers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-components, clean up LESS - Replace antd Card/Row/Col/Skeleton/Alert/Button/Divider/Typography with @openmetadata/ui-core-components equivalents - Replace @ant-design/icons InfoCircleOutlined with @untitledui/icons InfoCircle - Remove all .ant-* selectors from LESS; replace Less variable refs with CSS custom properties (--color-*) - Update skeleton test selector from .ant-skeleton.ant-skeleton-active to [aria-hidden="true"] (core Skeleton uses aria-hidden) - Update padding class test from .ant-col/.p-t-sm to .tw\\:pt-2 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated 7 test files to import from @react-awesome-query-builder/ui instead of @react-awesome-query-builder/antd, and replaced AntdConfig with BasicConfig. Applied UI checkstyle (organize-imports, lint:fix, prettier) on all modified test files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…invalid type cast, wire JSONLogicSearchClassBase to OMConfig - OMDateWidget: replace getInputType(operator) with fieldType prop — operator values like "equal"/"less" never contain "time"/"datetime"; fieldType is the correct discriminant - OMDateWidget: fix tw:bg-disabled_subtle → tw:bg-disabled-subtle (underscore → dash matches CSS token) - OMNumberWidget: remove impossible `as number & null` intersection cast; Number(v) is already number - JSONLogicSearchClassBase: import OMConfig and set baseConfig = OMConfig so JSON-logic query builder uses OM-styled widgets consistently with AdvancedSearchClassBase Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ocument native input in OMDateWidget
❌ PR checklist incompleteThis PR cannot be merged until the following are addressed on its linked issue:
The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically. Maintainers can bypass this check by adding the |
| async (search: string) => { | ||
| if (!asyncFetch) { | ||
| return; | ||
| } | ||
| const result = await asyncFetch(search); | ||
| setAllItems( | ||
| (result.values as ListItem[]).map((item) => ({ | ||
| id: String(item.value), | ||
| label: String(item.title ?? item.value), | ||
| })) | ||
| ); | ||
| }, | ||
| [asyncFetch] | ||
| ); |
There was a problem hiding this comment.
The multi-select adapter always calls asyncFetch(search) and discards the returned hasMore state. Fetchers such as enum/custom-property autocomplete accept an offset and can return more pages, so values after the first page can never be loaded or selected in the query builder.
| useEffect(() => { | ||
| const currentIds = new Set(selectedItems.items.map((i) => i.id)); | ||
| const targetIds = new Set(valueArray); | ||
|
|
||
| for (const id of targetIds) { | ||
| if (!currentIds.has(id)) { | ||
| const item = allItems.find((i) => i.id === id); | ||
| if (item) { | ||
| selectedItems.append(item); | ||
| } | ||
| } | ||
| } | ||
| for (const item of selectedItems.items) { | ||
| if (!targetIds.has(item.id)) { | ||
| selectedItems.remove(item.id); | ||
| } | ||
| } | ||
| }, [valueArray.join(',')]); |
There was a problem hiding this comment.
When a saved async multiselect filter is rendered, allItems is initially empty, so the sync effect cannot append chips for the current valueArray. After the async options arrive, the effect does not rerun for the same value, leaving persisted owner, tag, tier, or custom-property filters visually unselected even though the tree still contains their values.
| value={value !== null && value !== undefined ? String(value) : ''} | ||
| onChange={(v: string) => setValue(v === '' ? null : Number(v))} |
There was a problem hiding this comment.
Intermediate Numbers Store NaN
Number(v) is stored for every non-empty number-input string. Browser number inputs can emit intermediate values like 1e, -, or ., which convert to NaN; that value then enters the query tree and can produce an invalid or non-matching generated filter.
| value={value !== null && value !== undefined ? String(value) : ''} | |
| onChange={(v: string) => setValue(v === '' ? null : Number(v))} | |
| value={value !== null && value !== undefined ? String(value) : ''} | |
| onChange={(v: string) => { | |
| if (v === '') { | |
| setValue(null); | |
| return; | |
| } | |
| const nextValue = Number(v); | |
| if (Number.isFinite(nextValue)) { | |
| setValue(nextValue); | |
| } | |
| }} |
| return; | ||
| } | ||
| const result = await asyncFetch(search); | ||
| const fetched = (result.values as ListItem[]).map((item) => ({ |
There was a problem hiding this comment.
loadAsync still only calls asyncFetch(search), so the enum custom-property fetcher always uses its first-page offset. That fetcher can return more results through hasMore, but this widget drops that metadata and never requests the next offset. When an enum custom property has more values than the first response, users cannot browse or select later values unless their search text narrows the value into page one.
| if (!asyncFetch) { | ||
| return; | ||
| } | ||
| const result = await asyncFetch(search); |
There was a problem hiding this comment.
This still requests only the first async page. Enum custom-property fetchers accept an offset and return hasMore, but this call always uses the default offset and the result handling only stores the returned values. When a multiselect field has more values than the first response, users cannot browse or select later values unless their search text narrows the value into page one.
| if (!isNaN(num)) { | ||
| setValue(num); | ||
| } |
There was a problem hiding this comment.
This blocks NaN, but it still lets non-finite values into the query tree. A number input can accept exponent notation; typing 1e309 makes Number(v) become Infinity, and !isNaN(Infinity) is true, so setValue(Infinity) still runs. That leaves RAQB state with a value that cannot serialize as a valid query number.
| if (!isNaN(num)) { | |
| setValue(num); | |
| } | |
| if (Number.isFinite(num)) { | |
| setValue(num); | |
| } |
|
| Count | Rule |
|---|---|
| 35 | react-hooks/exhaustive-deps |
| 22 | @typescript-eslint/no-explicit-any |
| 8 | sonarjs/cyclomatic-complexity |
| 7 | openmetadata-imports/no-lower-layer-page-imports |
| 6 | sonarjs/no-duplicate-string |
| 5 | openmetadata-imports/no-impure-pure-utils |
| 4 | jsx-a11y/control-has-associated-label |
| 4 | openmetadata-imports/no-circular-imports |
| 4 | sonarjs/cognitive-complexity |
| 4 | @typescript-eslint/no-non-null-assertion |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.test.tsx:40:6 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.test.tsx:110:8 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx:99:5 |
react-hooks/exhaustive-deps |
React Hook useCallback has missing dependencies: 'form' and 'onChange'. Either include them or remove the dependency array. If 'onChange' changes too often, fin |
| 🟡 | src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx:158:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'form'. Either include it or remove the dependency array. |
| 🟡 | src/components/DataContract/ContractSemanticFormTab/ContractSemanticFormTab.tsx:181:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'onChange'. Either include it or remove the dependency array. If 'onChange' changes too often, find the parent co |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:37:1 |
openmetadata-imports/no-lower-layer-page-imports |
Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here. |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:131:5 |
react-hooks/exhaustive-deps |
React Hook useMemo has a missing dependency: 'config'. Either include it or remove the dependency array. |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:160:6 |
react-hooks/exhaustive-deps |
React Hook useMemo has a missing dependency: 'config'. Either include it or remove the dependency array. |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:184:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'searchOutputType'. Either include it or remove the dependency array. If 'setConfig' needs the current value of ' |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:204:9 |
react-hooks/exhaustive-deps |
The 'toggleModal' function makes the dependencies of useMemo Hook (at line 356) change on every render. Move it inside the useMemo callback. Alternatively, wrap |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:308:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'tabsInfo'. Either include it or remove the dependency array. |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:312:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'loadData'. Either include it or remove the dependency array. |
| 🟡 | src/components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.component.tsx:325:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has missing dependencies: 'handleReset' and 'loadTree'. Either include them or remove the dependency array. |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:68:9 |
react-hooks/exhaustive-deps |
The 'selectedResource' logical expression could make the dependencies of useMemo Hook (at line 77) change on every render. To fix this, wrap the initialization |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:68:9 |
react-hooks/exhaustive-deps |
The 'selectedResource' logical expression could make the dependencies of useMemo Hook (at line 135) change on every render. To fix this, wrap the initialization |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:68:9 |
react-hooks/exhaustive-deps |
The 'selectedResource' logical expression could make the dependencies of useEffect Hook (at line 154) change on every render. To fix this, wrap the initializati |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:121:5 |
react-hooks/exhaustive-deps |
React Hook useCallback has an unnecessary dependency: 'getExpandedResourceList'. Either exclude it or remove the dependency array. Outer scope values like 'getE |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:124:37 |
react-hooks/exhaustive-deps |
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:172:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has missing dependencies: 'config', 'fqn', 'onTreeUpdate', and 'queryFilter'. Either include them or remove the dependency array. |
| 🟡 | src/components/MyData/Widgets/CuratedAssetsWidget/AdvancedAssetsFilterField/AdvancedAssetsFilterField.component.tsx:187:13 |
jsx-a11y/label-has-for |
Form label must have ALL of the following types of associated control: nesting, id |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:41:1 |
openmetadata-imports/no-lower-layer-page-imports |
Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here. |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:68:77 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 14 which is greater than 10 authorized.","cost":4,"secondaryLocations":[{"line":68,"column":76,"endLine":68,"endColumn" |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:130:5 |
react-hooks/exhaustive-deps |
React Hook useCallback has a missing dependency: 'config'. Either include it or remove the dependency array. |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:144:5 |
react-hooks/exhaustive-deps |
React Hook useMemo has a missing dependency: 'searchResults'. Either include it or remove the dependency array. |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:219:6 |
react-hooks/exhaustive-deps |
React Hook useCallback has missing dependencies: 'debouncedFetchEntityCount', 'defaultField', 'onTreeUpdate', and 'subField'. Either include them or remove the |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:223:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has missing dependencies: 'onChangeSearchIndex' and 'resolvedSearchIndex'. Either include them or remove the dependency array. |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:229:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'loadDefaultValueInTree'. Either include it or remove the dependency array. |
| 🟡 | src/components/common/Form/JSONSchema/JsonSchemaWidgets/QueryBuilderWidget/QueryBuilderWidget.tsx:235:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array. However, 'props' will change when any prop changes, |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.test.tsx:109:39 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.test.tsx:118:52 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.test.tsx:126:30 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.test.tsx:126:43 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.test.tsx:144:18 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.test.tsx:362:34 |
@typescript-eslint/no-explicit-any |
Unexpected any. Specify a different type. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.tsx:49:1 |
openmetadata-imports/no-lower-layer-page-imports |
Pages are route-level composition modules. Move the shared implementation/type to a lower layer instead of importing a page from here. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.tsx:200:5 |
react-hooks/exhaustive-deps |
React Hook useCallback has a missing dependency: 'config'. Either include it or remove the dependency array. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.tsx:210:7 |
sonarjs/expression-complexity |
Reduce the number of conditional operators (4) used in the expression (maximum allowed 3). |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.tsx:215:5 |
react-hooks/exhaustive-deps |
React Hook useMemo has a missing dependency: 'searchResults'. Either include it or remove the dependency array. |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.tsx:261:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'props'. Either include it or remove the dependency array. However, 'props' will change when any prop changes, |
| 🟡 | src/components/common/QueryBuilderWidgetV1/QueryBuilderWidgetV1.tsx:337:13 |
jsx-a11y/control-has-associated-label |
A control must be associated with a text label. |
| 🟡 | src/components/common/TagsSection/TagsSection.test.tsx:143:15 |
jsx-a11y/control-has-associated-label |
A control must be associated with a text label. |
| 🟡 | src/utils/AdvancedSearchClassBase.ts:48:1 |
openmetadata-imports/no-circular-imports |
This runtime import participates in a circular dependency. Extract the shared type/constant/utility or invert the dependency. |
| 🟡 | src/utils/AdvancedSearchClassBase.ts:280:16 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/utils/AdvancedSearchClassBase.ts:611:51 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/utils/AdvancedSearchClassBase.ts:946:26 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/utils/AdvancedSearchClassBase.ts:1360:11 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 21 which is greater than 10 authorized.","cost":11,"secondaryLocations":[{"line":1360,"column":10,"endLine":1360,"endCo |
| 🟡 | src/utils/AdvancedSearchPureUtils.ts:16:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
| 🟡 | src/utils/AdvancedSearchPureUtils.ts:17:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
| 🟡 | src/utils/AdvancedSearchPureUtils.ts:18:1 |
openmetadata-imports/no-impure-pure-utils |
Pure utilities must not depend on React, UI, state, hooks, pages, or REST clients. Move orchestration/rendering out or move shared types to a lower layer. |
| 🟡 | src/utils/AdvancedSearchPureUtils.ts:214:23 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 11 which is greater than 10 authorized.","cost":1,"secondaryLocations":[{"line":214,"column":22,"endLine":214,"endColum |
… and 59 more. Run make ui-checkstyle-changed locally for the full list.
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed| renderField: (props) => <OMFieldSelect {...props} />, | ||
| // RAQB passes the same FieldProps shape (including setField) to both | ||
| // field and operator renderers, so the same select component works for both. | ||
| renderOperator: (props) => <OMFieldSelect {...props} />, |
There was a problem hiding this comment.
Operator callback miswired
renderOperator still renders OMFieldSelect, whose selection handler calls setField(id). This renderer is used for the rule's operator slot, so selecting ==, !=, or another operator needs to update the operator value, not the field value. When a user changes an operator, the rule can keep the old operator or update the wrong slot, and the generated query can differ from the visible rule.
| if (!asyncFetch) { | ||
| return; | ||
| } | ||
| const result = await asyncFetch(search); |
There was a problem hiding this comment.
Pagination still missing
This still calls asyncFetch(search) without an offset and ignores the returned pagination state. Custom-property enum multiselect fetchers can return hasMore for later pages, but this widget never stores that state or requests the next offset. For enum fields with more than the first response page, users cannot browse or select later values unless their search narrows the value into page one.
| const num = Number(v); | ||
| if (!isNaN(num)) { | ||
| setValue(num); | ||
| } |
There was a problem hiding this comment.
Non-finite numbers persist
This rejects NaN, but it still allows non-finite numbers. A number input can accept exponent notation such as 1e309; Number('1e309') becomes Infinity, and !isNaN(Infinity) is true, so the widget writes Infinity into the query-builder state. Query serialization can then emit null or an invalid numeric filter instead of the value the user entered.
| const num = Number(v); | |
| if (!isNaN(num)) { | |
| setValue(num); | |
| } | |
| const num = Number(v); | |
| if (Number.isFinite(num)) { | |
| setValue(num); | |
| } |
| if (!asyncFetch) { | ||
| return; | ||
| } | ||
| const result = await asyncFetch(search); |
There was a problem hiding this comment.
This still requests only the first async page. Enum custom-property fetchers accept an offset and return hasMore, but this call always uses the default offset and the result handling ignores the pagination state. When a multiselect field has values beyond the first response page, users cannot browse or select those values unless their search narrows the value into page one.
| setValue(null); | ||
| } else { | ||
| const num = Number(v); | ||
| if (!isNaN(num)) { |
There was a problem hiding this comment.
This blocks NaN, but it still accepts non-finite numbers. A number input can accept exponent notation such as 1e309; Number('1e309') becomes Infinity, and !isNaN(Infinity) is true, so the widget writes Infinity into the query-builder state. Query serialization can then produce null or an invalid numeric filter instead of the value the user entered.
… groups
`Complex nested groups` set a Description Status / Is rule (an enum value) and
then did:
await ruleLocator2.locator('.rule--value input').fill('production');
That line was always a no-op: the value is already `Incomplete` from the
preceding selectOption, and the typed text was discarded on blur. It only
resolved because antd's Select rendered an inner
`input.ant-select-selection-search-input`.
After the antd -> core migration the value widget is a react-aria Select, whose
`.rule--value` contains a trigger `<button>` and a visually hidden `<select>` —
no `<input>` at all. So `fill()` waits for a locator that can never resolve and
burns the full 180s test timeout (x2 with CI retries, each capturing trace and
video of a stuck page).
This is what pushed the chromium-02 shard past its 1500s budget: the shard was
SIGTERM'd (exit 124) with 129/131 tests passed, `Complex nested groups` never
reporting and `Placeholder validation` never starting behind it in the same
worker queue.
Verified locally against a build of this branch served from the container:
CuratedAssets.spec.ts now runs 26/26 green in 3.2m.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| if (!asyncFetch) { | ||
| return; | ||
| } | ||
| const result = await asyncFetch(search); |
There was a problem hiding this comment.
This still only fetches the first async page. Enum custom-property multiselects use an async fetcher that accepts an offset and returns hasMore, but this call always uses the default offset and the widget drops the pagination metadata. When an enum field has more values than the first response page, users cannot browse or select later values unless their search text narrows the value into page one. Preserve the pagination state and add a path that requests and appends later pages.
| if (!isNaN(num)) { | ||
| setValue(num); | ||
| } |
There was a problem hiding this comment.
This guard still allows non-finite numbers into query-builder state. A number input can accept exponent notation such as 1e309; Number('1e309') becomes Infinity, and !isNaN(Infinity) is true, so setValue(Infinity) runs. Downstream serialization cannot produce a valid numeric query value from that state, which can leave the generated filter broken or non-matching.
| if (!isNaN(num)) { | |
| setValue(num); | |
| } | |
| if (Number.isFinite(num)) { | |
| setValue(num); | |
| } |
| if (!isNaN(num)) { | ||
| setValue(num); | ||
| } |
There was a problem hiding this comment.
This guard rejects NaN, but it still accepts non-finite numbers. A number input can accept exponent notation such as 1e309; Number('1e309') becomes Infinity, and !isNaN(Infinity) is true, so setValue(Infinity) writes an invalid numeric value into the query-builder state. Downstream query serialization cannot produce a valid numeric filter from that value.
| if (!isNaN(num)) { | |
| setValue(num); | |
| } | |
| if (Number.isFinite(num)) { | |
| setValue(num); | |
| } |
Code Review
|
| Compact |
|
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source
| if (!asyncFetch) { | ||
| return; | ||
| } | ||
| const result = await asyncFetch(search); |
There was a problem hiding this comment.
Pagination Still Missing
loadAsync still calls asyncFetch(search) without an offset and only stores result.values. Enum custom-property fetchers can return hasMore and use later offsets, but this widget drops that state and has no way to request the next page. When a multiselect field has more values than the first response, users cannot browse or select those later values unless their search text narrows the value into page one.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| setValue(null); | ||
| } else { | ||
| const num = Number(v); | ||
| if (!isNaN(num)) { |
There was a problem hiding this comment.
Non-Finite Numbers Persist
This guard still lets non-finite values into query-builder state. A number input can emit exponent notation such as 1e309; Number('1e309') becomes Infinity, and !isNaN(Infinity) is true, so setValue(Infinity) runs. The query formatter later places numeric values directly into term and range bodies, where Infinity cannot serialize as a valid JSON number and can produce a broken or non-matching filter.
| if (!isNaN(num)) { | |
| if (Number.isFinite(num)) { |
|



Summary
@react-awesome-query-builder/antdimports with@react-awesome-query-builder/ui(API-identical, same version) across 14 production files and 8 test filesOMConfigfromBasicConfigwith new widget factories backed byopenmetadata-ui-core-components, eliminating Ant Design widget rendering inside query builder rulesQueryBuilderWidgetV1outer shell (Card, Alert, Skeleton, Divider, Typography) from antd to core-componentsAdvancedSearchUtilsandQueryBuilderUtilsfrom antdButton+@ant-design/iconsto coreButton+@untitledui/iconsScreen.Recording.2026-08-24.at.9.11.07.PM.mov
Screen.Recording.2026-08-24.at.9.29.51.PM.mov
Screen.Recording.2026-08-24.at.9.31.14.PM.mov
New widgets (
src/utils/queryBuilderWidgets/)OMTextWidgetInputOMNumberWidgetInput(numeric)OMSelectWidgetSelectwith async adapterOMMultiSelectWidgetMultiSelect+useListDatafromreact-statelyOMBooleanWidgetToggleOMDateWidget<input type="date/datetime-local/time">(coreDateInputis not publicly exported and requires@internationalized/dateobjects incompatible with RAQB string values)OMFieldSelectSelect(field/operator picker)OMConjsButtonGroup(AND/OR conjunction)All assembled into
src/utils/QueryBuilderOMConfig.tsxwhich exportsOMConfig.Test plan
QueryBuilderWidgetV1tests passsrc/utils/queryBuilderWidgets/)🤖 Generated with Claude Code
Greptile Summary
This PR migrates the query builder UI from Ant Design to core components. The main changes are:
Confidence Score: 4/5
This is close, but the remaining query-builder issues should be fixed before merging.
Infinityor-Infinityin query state.Files Needing Attention: OMMultiSelectWidget.tsx and OMNumberWidget.tsx
Important Files Changed
NaN, but still accepts non-finite numeric values.itemscollection for async callers.Reviews (48): Last reviewed commit: "fix flacky playwright" | Re-trigger Greptile
Context used (3)