Skip to content

chore(ui): migrate data asset select list to untitled #27697

Open
chirag-madlani wants to merge 36 commits into
mainfrom
feat-ai-automations
Open

chore(ui): migrate data asset select list to untitled #27697
chirag-madlani wants to merge 36 commits into
mainfrom
feat-ai-automations

Conversation

@chirag-madlani

@chirag-madlani chirag-madlani commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes:

Fixes https://github.com/open-metadata/openmetadata-collate/issues/4464

I worked on ... because ...

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.

Summary by Gitar

  • DataAssetAsyncSelectList refactor:
    • Migrated from antd Select to Autocomplete with native scroll pagination and improved selection persistence via knownOptionsRef.
    • Replaced mode prop with multiple boolean and updated onChange to support nullability.
  • Form and widget enhancements:
    • Improved AsyncSelectWidget and PropertyValue to handle null values correctly and support proper entityRef resolution.
    • Adjusted ScheduleIntervalV1 to handle null/undefined cron values and fixed GridItem column spanning calculation.
  • Minor fixes:
    • Fixed app-worker.js cache filter logic and resolved CSS formatting issues in knowledge-center-layout.
  • Test suite updates:
    • Updated Playwright locators for KnowledgeCenter, metric, and customProperty tests to accommodate the Autocomplete UI changes.
    • Rewrote DataAssetAsyncSelectList component tests to mock Autocomplete interactions.

This will update automatically on new commits.

------|----------|
| openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx | Core migration from antd Select to Autocomplete; introduces native scroll pagination, knownOptionsRef for selection persistence, and controlled selectedItems state — but has a redundant placeholder prop and a missing useEffect dependency that may fail CI lint. |
| openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/AsyncSelectWidget.tsx | Updated to handle null onChange and build initialOptions from entityRef, but the value prop doesn't fall back to entityRef.id when fullyQualifiedName is absent, causing pre-populated values to be cleared by the selectedValue effect. |
| openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface.ts | Replaces antd DefaultOptionType with SelectItemType, adds required id/value fields, renames mode to multiple, and exposes popoverClassName — clean interface update. |
| openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx | Switches from mode to multiple prop, adds id to initialOptions, and handles null entityReference in onFinish — straightforward and correct. |
| openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx | Adds optional popoverProps to AutocompleteProps and spreads it before fixed className/size overrides — safe, well-typed addition using Omit to prevent size/className conflicts. |
| openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.test.tsx | Comprehensive test rewrite replacing antd selector queries with Autocomplete mock interactions; covers selection, multi-select, placeholder, and pagination scenarios. |

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant DA as DataAssetAsyncSelectList
    participant AC as Autocomplete
    participant API as Search API

    U->>AC: Type in search input
    AC->>DA: onSearchChange(value)
    DA->>DA: debouncedSearch(value)
    DA->>API: "fetchOptions(value, page=1)"
    API-->>DA: "{ data, paging }"
    DA->>DA: setOptions / knownOptionsRef.set()
    DA->>AC: "items={filteredOptions}"

    U->>AC: Scroll near bottom of dropdown
    AC->>DA: popoverProps.onScroll(event)
    DA->>DA: handleNativeScroll → loadMoreOptions()
    DA->>API: fetchOptions(searchValue, currentPage+1)
    API-->>DA: "{ data, paging }"
    DA->>DA: setOptions(prev + data)

    U->>AC: Click item
    AC->>DA: onItemInserted(key)
    DA->>DA: handleItemInserted → setSelectedItems
    DA->>DA: onChange(selected)

    U->>AC: Remove chip
    AC->>DA: onItemCleared(key)
    DA->>DA: handleItemCleared → setSelectedItems
    DA->>DA: "onChange(updated | null)"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant DA as DataAssetAsyncSelectList
    participant AC as Autocomplete
    participant API as Search API

    U->>AC: Type in search input
    AC->>DA: onSearchChange(value)
    DA->>DA: debouncedSearch(value)
    DA->>API: "fetchOptions(value, page=1)"
    API-->>DA: "{ data, paging }"
    DA->>DA: setOptions / knownOptionsRef.set()
    DA->>AC: "items={filteredOptions}"

    U->>AC: Scroll near bottom of dropdown
    AC->>DA: popoverProps.onScroll(event)
    DA->>DA: handleNativeScroll → loadMoreOptions()
    DA->>API: fetchOptions(searchValue, currentPage+1)
    API-->>DA: "{ data, paging }"
    DA->>DA: setOptions(prev + data)

    U->>AC: Click item
    AC->>DA: onItemInserted(key)
    DA->>DA: handleItemInserted → setSelectedItems
    DA->>DA: onChange(selected)

    U->>AC: Remove chip
    AC->>DA: onItemCleared(key)
    DA->>DA: handleItemCleared → setSelectedItems
    DA->>DA: "onChange(updated | null)"
Loading

Reviews (1): Last reviewed commit: "Merge branch 'main' into feat-ai-automat..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Greptile Summary

This PR migrates DataAssetAsyncSelectList from Ant Design's Select to the internal Autocomplete component, replacing the mode prop with multiple, implementing native scroll-based pagination via knownOptionsRef, and updating all call sites and tests accordingly.

  • Core component (DataAssetAsyncSelectList.tsx): Replaces antd Select with Autocomplete; introduces knownOptionsRef for selection persistence across option-list refreshes and handleNativeScroll/loadMoreOptions for scroll-triggered pagination. The loadMoreOptions guard does not handle the uninitialized paging.total case (see inline comment).
  • Call sites & supporting files: PropertyValue.tsx, RelatedDataAssetsForm.tsx, RelatedMetricsForm.tsx, and AsyncSelectWidget.tsx are all updated to the new multiple boolean prop and null-aware onChange signature; placeholder labels in PropertyValue.tsx are also corrected (previously inverted singular/plural).
  • Tests: DataAssetAsyncSelectList.test.tsx is rewritten to mock Autocomplete interactions; Playwright locators in KnowledgeCenter.ts, metric.ts, and customProperty.ts are updated to remove Ant Design class-based selectors.

Confidence Score: 4/5

Safe to merge once the loadMoreOptions pagination guard is fixed; all other changes are correct and well-tested.

The scroll-triggered loadMoreOptions can race against the initial loadOptions fetch because the paging.total === undefined case is not guarded. A user who opens the dropdown and scrolls before the first page arrives will trigger a page-2 fetch whose results are then silently discarded when page 1 completes. All other changes — the Autocomplete migration, prop renaming, null-handling, and test rewrites — look correct.

openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx — specifically the loadMoreOptions guard.

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx Core migration from antd Select to Autocomplete; introduces native scroll pagination via knownOptionsRef and controlled selectedItems state. Contains a guard bug in loadMoreOptions where an uninitialized paging.total allows premature page-2 fetches that are then overwritten by the completing page-1 load.
openmetadata-ui/src/main/resources/ui/src/components/common/Form/JSONSchema/JsonSchemaWidgets/AsyncSelectWidget.tsx Updated to handle null onChange and build initialOptions from entityRef; value now falls back to entityRef.id via resolvedFqn.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface.ts Clean interface update: replaces antd DefaultOptionType with SelectItemType, adds required id/value fields, renames mode to multiple, and exposes popoverClassName/popoverProps.
openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx Switches from mode to multiple prop, adds id to initialOptions, fixes inverted singular/plural placeholder labels, and handles null entityReference in onFinish — all correct.
openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx Adds optional popoverProps to AutocompleteProps and spreads it before fixed className/size overrides — safe, well-typed addition using Omit to prevent size/className conflicts.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.test.tsx Comprehensive test rewrite: mocks Autocomplete interactions, adds multi-select coverage, and switches assertions to data-testid/role queries aligned with the new component API.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant DA as DataAssetAsyncSelectList
    participant AC as Autocomplete
    participant API as Search API

    Note over DA: Mount: selectedItems = initialOptions
    DA->>API: loadOptions('') [page 1]
    Note over DA: selectedValue effect runs

    U->>AC: Open dropdown / scroll near bottom
    AC->>DA: handleNativeScroll
    Note over DA: ⚠ paging.total=undefined, guard fails
    DA->>API: loadMoreOptions() [page 2] race
    API-->>DA: page 2 data appended to options
    API-->>DA: page 1 completes → setOptions(page1) overwrites!

    U->>AC: Type in search input
    AC->>DA: onSearchChange(value)
    DA->>DA: debouncedSearch(value)
    DA->>API: "loadOptions(value, page=1)"
    API-->>DA: data + paging
    DA->>DA: setOptions / knownOptionsRef.set()

    U->>AC: Click item
    AC->>DA: onItemInserted(key)
    DA->>DA: find in filteredOptions → setSelectedItems
    DA->>DA: onChange(selected)

    U->>AC: Remove chip
    AC->>DA: onItemCleared(key)
    DA->>DA: "filter selectedItems → onChange(updated | null)"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant DA as DataAssetAsyncSelectList
    participant AC as Autocomplete
    participant API as Search API

    Note over DA: Mount: selectedItems = initialOptions
    DA->>API: loadOptions('') [page 1]
    Note over DA: selectedValue effect runs

    U->>AC: Open dropdown / scroll near bottom
    AC->>DA: handleNativeScroll
    Note over DA: ⚠ paging.total=undefined, guard fails
    DA->>API: loadMoreOptions() [page 2] race
    API-->>DA: page 2 data appended to options
    API-->>DA: page 1 completes → setOptions(page1) overwrites!

    U->>AC: Type in search input
    AC->>DA: onSearchChange(value)
    DA->>DA: debouncedSearch(value)
    DA->>API: "loadOptions(value, page=1)"
    API-->>DA: data + paging
    DA->>DA: setOptions / knownOptionsRef.set()

    U->>AC: Click item
    AC->>DA: onItemInserted(key)
    DA->>DA: find in filteredOptions → setSelectedItems
    DA->>DA: onChange(selected)

    U->>AC: Remove chip
    AC->>DA: onItemCleared(key)
    DA->>DA: "filter selectedItems → onChange(updated | null)"
Loading

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Copilot AI review requested due to automatic review settings April 24, 2026 09:40
@chirag-madlani chirag-madlani requested review from a team and karanh37 as code owners April 24, 2026 09:40
@github-actions github-actions Bot added safe to test Add this label to run secure Github workflows on PRs UI UI specific issues labels Apr 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates parts of the OpenMetadata UI to align with newer component patterns (notably the UI-core Autocomplete) and adjusts scheduler cron handling to use null rather than undefined in some flows.

Changes:

  • Refactors DataAssetAsyncSelectList from Ant Design Select to @openmetadata/ui-core-components Autocomplete, adding load-more behavior and new prop types.
  • Updates scheduler interval state to use cron: null in some places, and replaces global isNaN with Number.isNaN in scheduler utils.
  • Exports GridItem from the UI core grid component.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx Uses Number.isNaN for safer NaN checks when normalizing cron fields.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleIntervalV1.tsx Switches “On Demand” cron handling to null and adjusts prop typing for onChange.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleInterval.interface.ts Changes cron typing to string | null (non-optional) for workflow config and state.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx Migrates async select UI to core Autocomplete, reworks selection state and load-more mechanics.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.test.tsx Updates tests for new props (multiple) but still assumes old AntD DOM behaviors.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface.ts Updates option/props types to use UI-core SelectItemType and replaces mode with multiple.
openmetadata-ui-core-components/src/main/resources/ui/src/components/base/grid/grid.tsx Exports GridItem and tweaks start/span clamping logic formatting.

Comment on lines +224 to +242
const renderCustomTag = useCallback(
(item: SelectItemType, onRemove: () => void) => {
const dataAssetItem = item as DataAssetOption;
if (renderTag) {
return renderTag(item, onRemove);
}

if (
searchIndex === SearchIndex.USER ||
searchIndex === SearchIndex.TEAM ||
dataAssetItem.reference?.type === EntityType.USER ||
dataAssetItem.reference?.type === EntityType.TEAM
) {
return null;
}

const dropdownRender = (menu: React.ReactElement) => (
<>
{menu}
{hasContentLoading ? <Loader size="small" /> : null}
</>
return null;
},
[renderTag, searchIndex]
Comment on lines +264 to +281
useEffect(() => {
const popoverElement = popoverRef.current;
if (popoverElement) {
popoverElement.addEventListener(
'scroll',
handlePopoverScroll as unknown as EventListener
);

selectedDataAssetsRef.current = selectedOptions;
onChange?.(selectedOptions);
} else {
onChange?.(options as DataAssetOption);
return () => {
popoverElement.removeEventListener(
'scroll',
handlePopoverScroll as unknown as EventListener
);
};
}
};

const handleBlur = useCallback(() => {
setCurrentPage(1);
setSearchValue('');
setOptions([]);
}, []);
return () => {};
}, [handlePopoverScroll]);
(key: string) => {
const updatedSelection = selectedItems.filter((item) => item.id !== key);
setSelectedItems(updatedSelection);
onChange?.(multiple ? updatedSelection : updatedSelection[0]);
Comment on lines 45 to 68
export interface WorkflowExtraConfig {
cron?: string;
cron: string | null;
enableDebugLog?: boolean;
}

export interface IngestionExtraConfig {
retries?: number;
raiseOnError?: boolean;
}

export interface Combination {
hour: RegExp;
day: RegExp;
week: RegExp;
month: RegExp;
}
export interface StateValue {
selectedPeriod: string;
hour: string;
min: string;
dow: string;
dom: string;
cron?: string;
cron: string | null;
}
Comment on lines 46 to 58
@@ -44,17 +53,19 @@
value: selectedValue,
filterFqns = [],
queryFilter,
renderTag,
...props
}) => {
Comment on lines +64 to +66
const [selectedItems, setSelectedItems] = useState<DataAssetOption[]>(
initialOptions ?? []
);
Comment on lines +31 to +46
@@ -39,4 +43,5 @@ export interface DataAssetAsyncSelectListProps {
onChange?: (option: DataAssetOption | DataAssetOption[]) => void;
filterFqns?: string[];
queryFilter?: Record<string, unknown>;
renderTag?: (item: SelectItemType, onRemove: () => void) => ReactNode;
import { StateValue } from './ScheduleInterval.interface';

export interface ScheduleIntervalV1Props {
value?: string;
Comment on lines +207 to +216
if (isString(selectedValue) || isArray(selectedValue)) {
const values = isArray(selectedValue) ? selectedValue : [selectedValue];
const items = values
.map((val) => filteredOptions.find((opt) => opt.value === val))
.filter(Boolean) as DataAssetOption[];
setSelectedItems(items);
} else if (selectedValue) {
setSelectedItems(
isArray(selectedValue) ? selectedValue : [selectedValue]
);
Comment on lines 146 to 148
const { container } = render(
<DataAssetAsyncSelectList
mode="multiple"
searchIndex={SearchIndex.USER}
/>
<DataAssetAsyncSelectList multiple searchIndex={SearchIndex.USER} />
);
@github-actions

github-actions Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 1 failure(s), 8 flaky

✅ 3453 passed · ❌ 1 failed · 🟡 8 flaky · ⏭️ 76 skipped

Shard Passed Failed Flaky Skipped
🟡 Shard 1 300 0 2 4
✅ Shard 2 815 0 0 9
🔴 Shard 3 813 1 3 8
✅ Shard 5 733 0 0 47
🟡 Shard 6 792 0 3 8

Genuine Failures (failed on all attempts)

Features/KnowledgeCenter.spec.ts › Knowledge Center page (shard 3)
�[31mTest timeout of 180000ms exceeded.�[39m
🟡 8 flaky test(s) (passed on retry)
  • Flow/Tour.spec.ts › Tour should work from help section (shard 1, 2 retries)
  • Pages/Roles.spec.ts › Roles page should work properly (shard 1, 1 retry)
  • Features/KnowledgeCenterTextEditor.spec.ts › Rich Text Editor - Text Formatting (shard 3, 1 retry)
  • Features/Tasks/DomainFiltering.spec.ts › selecting All Domains removes domain filter from feed API call (shard 3, 1 retry)
  • Features/Tasks/TaskNavigation.spec.ts › navigating to /table/TASK-XXXXX should show 404 (invalid URL pattern) (shard 3, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: searchIndex (shard 6, 1 retry)
  • Pages/Lineage/PlatformLineage.spec.ts › Verify domain platform view (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Copilot AI review requested due to automatic review settings April 27, 2026 05:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates parts of the OpenMetadata UI to align selection controls with @openmetadata/ui-core-components and refines scheduler/grid behaviors.

Changes:

  • Migrates DataAssetAsyncSelectList from AntD Select to UI core Autocomplete, updating call sites to use a multiple boolean.
  • Updates scheduler interval types/behavior to represent “on demand” schedules via null rather than undefined.
  • Adjusts core grid item start/span clamping logic and exports GridItem.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx Uses Number.isNaN for cron parsing guards.
openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/PropertyValue.tsx Updates DataAssetAsyncSelectList usage from mode to multiple.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleIntervalV1.tsx Switches “on demand” schedule handling to null and adjusts cron input binding.
openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleInterval.interface.ts Changes cron typing for scheduler state/extra config.
openmetadata-ui/src/main/resources/ui/src/components/Metric/RelatedMetrics/RelatedMetricsForm.tsx Updates DataAssetAsyncSelectList usage to multiple.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx Replaces AntD Select implementation with UI core Autocomplete and adds async loading + custom item rendering.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.test.tsx Updates tests to the new multiple prop and option shape.
openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface.ts Updates option/props types to match UI core SelectItemType and replaces mode with multiple.
openmetadata-ui-core-components/src/main/resources/ui/src/components/base/grid/grid.tsx Exports GridItem and refines start/span clamping logic.
Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.test.tsx:153

  • This test still interacts with AntD Select internals (e.g., querying .ant-select-selector), but DataAssetAsyncSelectList is now based on @openmetadata/ui-core-components Autocomplete, so these selectors/click paths won’t work and the test will fail. Update the test to open the Autocomplete popover via its actual input/button (query by role/testid) and select items using the rendered Autocomplete.Item elements; also align mocked search hits with the new code path (it reads _source.type rather than _source.entityType).
    const { container } = render(
      <DataAssetAsyncSelectList multiple searchIndex={SearchIndex.USER} />
    );

    await act(async () => {
      const inputBox = container.querySelector('.ant-select-selector');
      inputBox && fireEvent.click(inputBox);
    });

import { StateValue } from './ScheduleInterval.interface';

export interface ScheduleIntervalV1Props {
value?: string;

export interface WorkflowExtraConfig {
cron?: string;
cron: string | null;
Comment on lines 216 to 223
const items = values
.map((val) => filteredOptions.find((opt) => opt.value === val))
.filter(Boolean) as DataAssetOption[];
setSelectedItems(items);
} else if (selectedValue) {
setSelectedItems(
isArray(selectedValue) ? selectedValue : [selectedValue]
);
Comment on lines +64 to +65
const [selectedItems, setSelectedItems] = useState<string[]>(
initialOptions?.map((options) => options.value) ?? []
(key: Key) => {
const updatedSelection = selectedItems.filter((item) => item.id !== key);
setSelectedItems(updatedSelection);
onChange?.(multiple ? updatedSelection : updatedSelection[0]);
Comment on lines +253 to +270
useEffect(() => {
const popoverElement = popoverRef.current;
if (popoverElement) {
popoverElement.addEventListener(
'scroll',
handlePopoverScroll as unknown as EventListener
);

return () => {
popoverElement.removeEventListener(
'scroll',
handlePopoverScroll as unknown as EventListener
);
};
}
const selectedOption = selectedValue as DataAssetOption;

return selectedOption?.value as string;
}, [mode, selectedValue]);
return () => {};
}, [handlePopoverScroll]);
Comment on lines 711 to 714
<DataAssetAsyncSelectList
initialOptions={initialOptions}
mode={mode}
multiple={multiple}
placeholder={
dow: string;
dom: string;
cron?: string;
cron: string | null;
Copilot AI review requested due to automatic review settings May 4, 2026 15:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

import { HintText } from '@/components/base/input/hint-text';
import { Label } from '@/components/base/input/label';
import { Popover } from '@/components/base/select/popover';
import type { Popover, PopoverProps } from '@/components/base/select/popover';
{hasContentLoading ? <Loader size="small" /> : null}
</>
if (multiple) {
setSelectedItems((prev) => {
Comment on lines +226 to +234
const updatedSelection = selectedItems.filter((item) => item.id !== key);
setSelectedItems(updatedSelection);
if (multiple) {
onChange?.(updatedSelection);
} else {
onChange?.(updatedSelection[0] ?? null);
}
},
[selectedItems, multiple, onChange]
Comment on lines +250 to +256
if (isString(arr[0])) {
// Array of FQN strings
const items = (arr as string[])
.map((val) => knownOptionsRef.current.get(val))
.filter(Boolean) as DataAssetOption[];
if (items.length > 0) {
setSelectedItems(items);
Comment on lines +262 to +267
} else if (isString(selectedValue)) {
// Single FQN string
const item = knownOptionsRef.current.get(selectedValue);
if (item) {
setSelectedItems([item]);
}
Comment on lines 34 to 40
return (
<DataAssetAsyncSelectList
defaultValue={props?.value?.fullyQualifiedName ?? ''}
placeholder={schema.placeholder ?? ''}
searchIndex={schema?.autoCompleteType ?? SearchIndex.TABLE}
value={props?.value?.fullyQualifiedName ?? undefined}
onChange={handleChange}
/>
Comment on lines +312 to +319
placeholder={props.placeholder}
popoverClassName={customPopoverClassName}
popoverProps={popoverProps}
selectedItems={selectedItems}
onItemCleared={handleItemCleared}
onItemInserted={handleItemInserted}
onSearchChange={handleSearchChange}
{...props}>
@chirag-madlani

Copy link
Copy Markdown
Collaborator Author

@copilot apply changes based on the comments in this thread

Copilot AI and others added 2 commits May 4, 2026 15:39
…tional updates, props spread, initialOptions, cron null handling

Agent-Logs-Url: https://github.com/open-metadata/OpenMetadata/sessions/57a8b4a8-1bed-44ec-a404-7e71697033d9

Co-authored-by: chirag-madlani <12962843+chirag-madlani@users.noreply.github.com>
… fallback, compose scroll handlers

Agent-Logs-Url: https://github.com/open-metadata/OpenMetadata/sessions/57a8b4a8-1bed-44ec-a404-7e71697033d9

Co-authored-by: chirag-madlani <12962843+chirag-madlani@users.noreply.github.com>

Copilot AI commented May 4, 2026

Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments in this thread

Applied all changes from the review thread in commits 77cee6f8 and 55947bf6. Summary:

  • autocomplete.tsx: Fixed Popover from import type to a value import — type-only imports cannot be used as JSX elements at runtime
  • DataAssetAsyncSelectList.tsx:
    • handleItemInserted: Added duplicate check (prev.some(i => i.id === item.id)) before appending in multi-select mode
    • handleItemCleared: Uses functional setSelectedItems(prev => ...) to avoid stale closure
    • Destructured popoverClassName/popoverProps from ...props and moved {...props} before explicit props — prevents callers from accidentally overriding popoverClassName or popoverProps
    • Composed scroll handlers in popoverProps so caller's onScroll is not silently discarded
    • Extracted createPlaceholderOption(fqn) utility — used when a FQN string in selectedValue isn't yet in knownOptionsRef, ensuring the UI always renders the current selection even before options load
    • Changed imports to import type for PopoverProps and SelectItemType
  • AsyncSelectWidget.tsx: Constructs initialOptions from props.value (EntityReference) with consistent id/value fallback chain — ensures existing form values render correctly on first paint
  • ScheduleInterval.interface.ts: Changed cron?: string | nullcron?: string in WorkflowExtraConfig and StateValue — prevents null from being serialized as scheduleInterval: null in API payloads
  • ScheduleIntervalV1.tsx: Changed onChange?.(null)onChange?.(undefined) for ON_DEMAND — undefined is omitted during JSON serialization, matching existing backend expectations

1 similar comment

Copilot AI commented May 4, 2026

Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments in this thread

Applied all changes from the review thread in commits 77cee6f8 and 55947bf6. Summary:

  • autocomplete.tsx: Fixed Popover from import type to a value import — type-only imports cannot be used as JSX elements at runtime
  • DataAssetAsyncSelectList.tsx:
    • handleItemInserted: Added duplicate check (prev.some(i => i.id === item.id)) before appending in multi-select mode
    • handleItemCleared: Uses functional setSelectedItems(prev => ...) to avoid stale closure
    • Destructured popoverClassName/popoverProps from ...props and moved {...props} before explicit props — prevents callers from accidentally overriding popoverClassName or popoverProps
    • Composed scroll handlers in popoverProps so caller's onScroll is not silently discarded
    • Extracted createPlaceholderOption(fqn) utility — used when a FQN string in selectedValue isn't yet in knownOptionsRef, ensuring the UI always renders the current selection even before options load
    • Changed imports to import type for PopoverProps and SelectItemType
  • AsyncSelectWidget.tsx: Constructs initialOptions from props.value (EntityReference) with consistent id/value fallback chain — ensures existing form values render correctly on first paint
  • ScheduleInterval.interface.ts: Changed cron?: string | nullcron?: string in WorkflowExtraConfig and StateValue — prevents null from being serialized as scheduleInterval: null in API payloads
  • ScheduleIntervalV1.tsx: Changed onChange?.(null)onChange?.(undefined) for ON_DEMAND — undefined is omitted during JSON serialization, matching existing backend expectations

@gitar-bot

gitar-bot Bot commented May 26, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 10 resolved / 11 findings

Refactors DataAssetAsyncSelectList and component library props to fix scroll event handling, typing issues, and Playwright selector reliability. Address the lingering useEffect logic flaw in loadOptions where empty dependency arrays prevent re-triggering on prop changes.

⚠️ Bug: useEffect with empty deps skips loadOptions on prop changes

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:227-232

The hasInitiallyLoaded ref + empty dependency array pattern (lines 227-232) means loadOptions is called exactly once on mount. If searchIndex or queryFilter props change after mount, the options list will never refresh. The previous code had [loadOptions] as the dependency, which would re-fetch when those props changed.

If this is intentional (to fix the infinite re-render from the previous finding), consider adding searchIndex and queryFilter to the dependency array and resetting the ref when they change.

✅ 10 resolved
Bug: renderCustomTag always returns null for non-user/team entities

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:224-238
The renderCustomTag callback (lines 224-243) returns null for every code path except when renderTag is provided. For user/team entities it returns null (line 237), and for all other entities it also returns null (line 240). This means custom tag rendering is effectively a no-op and selected items will use whatever the Autocomplete component's default rendering is, which may not match the previous behavior.

Bug: Scroll listener never attaches — infinite scroll is broken

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:245 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:264-278 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:287-301
The useEffect at line 264 checks popoverRef.current to attach a scroll listener, but popoverRef.current is initially null and only gets set by the MutationObserver effect (line 287). Since ref mutations don't trigger re-renders, the scroll useEffect won't re-run when the ref is finally assigned. The handlePopoverScroll dependency may cause re-runs for unrelated reasons, but the timing is not guaranteed — the popover DOM element may not exist yet when those fire. This means infinite scroll (load-more-on-scroll) will silently not work in many cases.

Consider using a state variable instead of a ref to track the popover element, so that when the MutationObserver finds it, setting state triggers the scroll effect to re-run. Alternatively, attach the scroll listener directly inside the MutationObserver callback.

Bug: useEffect with filteredOptions dep risks infinite re-render loop

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:206-218
The useEffect at line 206 depends on filteredOptions, which is derived from options. When loadOptions('') runs on mount (line 221), it updates optionsfilteredOptions changes → this effect runs → setSelectedItems triggers a re-render. If selectedValue is a string/array and the .find() produces a new array reference each time, this creates repeated state updates.

While React may bail out if the state is shallow-equal, the .filter(Boolean) always creates a new array, so setSelectedItems will always enqueue an update. This can cause unnecessary re-renders on every options change (e.g., during search or load-more).

Consider removing filteredOptions from the dependency array or computing selected items only when selectedValue actually changes.

Bug: getStateValue assigns undefined to cron field typed as string|null

📄 openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx:142 📄 openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleInterval.interface.ts:67
After changing StateValue.cron from cron?: string to cron: string | null, the getStateValue function at line 142 still assigns cron: value where value is string | undefined. TypeScript should flag this as undefined is not assignable to string | null. This needs to be cron: value ?? null.

Bug: selectedItems typed as string[] but used as DataAssetOption[]

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:64-65 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:193-194 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:206 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:219-222
The state selectedItems was changed from DataAssetOption[] to string[] (line 64), but every consumer still treats it as DataAssetOption[]:

  1. handleItemInserted (line 193): [...selectedItems, item] pushes a DataAssetOption into a string[]
  2. handleItemCleared (line 206): selectedItems.filter((item) => item.id !== key) calls .id on a string
  3. useEffect (line 219-222): setSelectedItems(items) assigns DataAssetOption[] to string[] state
  4. selectedItems is passed to the component (line 303) which likely expects SelectItemType[], not string[]

This will either fail TypeScript compilation or cause runtime errors when .id is accessed on a string in handleItemCleared.

...and 5 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Refactors DataAssetAsyncSelectList and component library props to fix scroll event handling, typing issues, and Playwright selector reliability. Address the lingering useEffect logic flaw in loadOptions where empty dependency arrays prevent re-triggering on prop changes.

1. ⚠️ Bug: useEffect with empty deps skips loadOptions on prop changes
   Files: openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:227-232

   The `hasInitiallyLoaded` ref + empty dependency array pattern (lines 227-232) means `loadOptions` is called exactly once on mount. If `searchIndex` or `queryFilter` props change after mount, the options list will never refresh. The previous code had `[loadOptions]` as the dependency, which would re-fetch when those props changed.
   
   If this is intentional (to fix the infinite re-render from the previous finding), consider adding `searchIndex` and `queryFilter` to the dependency array and resetting the ref when they change.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

sonarqubecloud Bot commented Jun 1, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • Linked issue open-metadata/openmetadata-collate#4464 does not exist or is not accessible.

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 skip-pr-checks label.

Comment on lines +332 to +334
multiple={multiple}
placeholder={props.placeholder}
popoverClassName={customPopoverClassName}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 placeholder is already spread via {...props} on the line above, so this explicit prop is redundant. Both point to the same value, but it can mislead readers into thinking a different override is happening.

Suggested change
multiple={multiple}
placeholder={props.placeholder}
popoverClassName={customPopoverClassName}
multiple={multiple}
popoverClassName={customPopoverClassName}

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!

Comment on lines +288 to 293
useEffect(() => {
if (!hasInitiallyLoaded.current) {
hasInitiallyLoaded.current = true;
loadOptions('');
}
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing loadOptions in useEffect dependency array

loadOptions is captured from the outer scope but is not listed in the deps array []. ESLint's react-hooks/exhaustive-deps rule will flag this, and CI's lint-src check may fail. The hasInitiallyLoaded guard is intentional, but the correct pattern to satisfy the linter without changing behaviour is to list loadOptions in the deps — the ref guard still prevents re-execution.

chirag-madlani and others added 2 commits June 22, 2026 19:34
- AsyncSelectWidget: use relative import for getEntityName instead of the
  unresolvable `src/utils/EntityNameUtils` absolute path (broke the Vite
  production build and the FormBuilder/ApplicationConfiguration Jest suites)
- AsyncSelectWidget: pass resolvedFqn (id fallback) as value so a
  pre-populated entity without a fullyQualifiedName isn't cleared
- DataAssetAsyncSelectList.interface: declare the popoverProps prop the
  component already consumes
- RelatedDataAssetsForm: migrate the renamed mode="multiple" prop to multiple

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	openmetadata-ui-core-components/src/main/resources/ui/src/components/base/autocomplete/autocomplete.tsx
#	openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleIntervalV1.tsx
@gitar-bot

gitar-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 10 resolved / 11 findings

Refactors DataAssetAsyncSelectList and component library props to fix scroll event handling, typing issues, and Playwright selector reliability. Address the lingering useEffect logic flaw in loadOptions where empty dependency arrays prevent re-triggering on prop changes.

⚠️ Bug: useEffect with empty deps skips loadOptions on prop changes

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:227-232

The hasInitiallyLoaded ref + empty dependency array pattern (lines 227-232) means loadOptions is called exactly once on mount. If searchIndex or queryFilter props change after mount, the options list will never refresh. The previous code had [loadOptions] as the dependency, which would re-fetch when those props changed.

If this is intentional (to fix the infinite re-render from the previous finding), consider adding searchIndex and queryFilter to the dependency array and resetting the ref when they change.

✅ 10 resolved
Bug: renderCustomTag always returns null for non-user/team entities

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:224-238
The renderCustomTag callback (lines 224-243) returns null for every code path except when renderTag is provided. For user/team entities it returns null (line 237), and for all other entities it also returns null (line 240). This means custom tag rendering is effectively a no-op and selected items will use whatever the Autocomplete component's default rendering is, which may not match the previous behavior.

Bug: Scroll listener never attaches — infinite scroll is broken

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:245 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:264-278 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:287-301
The useEffect at line 264 checks popoverRef.current to attach a scroll listener, but popoverRef.current is initially null and only gets set by the MutationObserver effect (line 287). Since ref mutations don't trigger re-renders, the scroll useEffect won't re-run when the ref is finally assigned. The handlePopoverScroll dependency may cause re-runs for unrelated reasons, but the timing is not guaranteed — the popover DOM element may not exist yet when those fire. This means infinite scroll (load-more-on-scroll) will silently not work in many cases.

Consider using a state variable instead of a ref to track the popover element, so that when the MutationObserver finds it, setting state triggers the scroll effect to re-run. Alternatively, attach the scroll listener directly inside the MutationObserver callback.

Bug: useEffect with filteredOptions dep risks infinite re-render loop

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:206-218
The useEffect at line 206 depends on filteredOptions, which is derived from options. When loadOptions('') runs on mount (line 221), it updates optionsfilteredOptions changes → this effect runs → setSelectedItems triggers a re-render. If selectedValue is a string/array and the .find() produces a new array reference each time, this creates repeated state updates.

While React may bail out if the state is shallow-equal, the .filter(Boolean) always creates a new array, so setSelectedItems will always enqueue an update. This can cause unnecessary re-renders on every options change (e.g., during search or load-more).

Consider removing filteredOptions from the dependency array or computing selected items only when selectedValue actually changes.

Bug: getStateValue assigns undefined to cron field typed as string|null

📄 openmetadata-ui/src/main/resources/ui/src/utils/SchedularUtils.tsx:142 📄 openmetadata-ui/src/main/resources/ui/src/components/Settings/Services/AddIngestion/Steps/ScheduleInterval.interface.ts:67
After changing StateValue.cron from cron?: string to cron: string | null, the getStateValue function at line 142 still assigns cron: value where value is string | undefined. TypeScript should flag this as undefined is not assignable to string | null. This needs to be cron: value ?? null.

Bug: selectedItems typed as string[] but used as DataAssetOption[]

📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:64-65 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:193-194 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:206 📄 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:219-222
The state selectedItems was changed from DataAssetOption[] to string[] (line 64), but every consumer still treats it as DataAssetOption[]:

  1. handleItemInserted (line 193): [...selectedItems, item] pushes a DataAssetOption into a string[]
  2. handleItemCleared (line 206): selectedItems.filter((item) => item.id !== key) calls .id on a string
  3. useEffect (line 219-222): setSelectedItems(items) assigns DataAssetOption[] to string[] state
  4. selectedItems is passed to the component (line 303) which likely expects SelectItemType[], not string[]

This will either fail TypeScript compilation or cause runtime errors when .id is accessed on a string in handleItemCleared.

...and 5 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Refactors DataAssetAsyncSelectList and component library props to fix scroll event handling, typing issues, and Playwright selector reliability. Address the lingering useEffect logic flaw in loadOptions where empty dependency arrays prevent re-triggering on prop changes.

1. ⚠️ Bug: useEffect with empty deps skips loadOptions on prop changes
   Files: openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.tsx:227-232

   The `hasInitiallyLoaded` ref + empty dependency array pattern (lines 227-232) means `loadOptions` is called exactly once on mount. If `searchIndex` or `queryFilter` props change after mount, the options list will never refresh. The previous code had `[loadOptions]` as the dependency, which would re-fetch when those props changed.
   
   If this is intentional (to fix the infinite re-render from the previous finding), consider adding `searchIndex` and `queryFilter` to the dependency array and resetting the ref when they change.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants