diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-account-card.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-account-card.tsx index f18b63ccf512..de7a63f8e7d6 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-account-card.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/app-navigation/base-components/nav-account-card.tsx @@ -250,6 +250,10 @@ export const NavAccountCard = ({ cx( 'tw:origin-(--trigger-anchor-point) tw:will-change-transform', diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/popover/popover.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/popover/popover.tsx index 175cc63cfc02..4a5917f73355 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/popover/popover.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/application/popover/popover.tsx @@ -70,6 +70,9 @@ export const Popover = ({ }: PopoverProps) => { return ( diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/dropdown/dropdown.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/dropdown/dropdown.tsx index 78aecd2dbe8e..34fe884f37d2 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/dropdown/dropdown.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/dropdown/dropdown.tsx @@ -141,6 +141,11 @@ const DropdownPopover = (props: DropdownPopoverProps) => { return ( diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/combobox.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/combobox.tsx index 5ab5dfe6e805..26061ab3f68b 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/combobox.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/combobox.tsx @@ -227,7 +227,16 @@ export const ComboBox = ({ return ( - + {/* items must live on the ComboBox (not the inner ListBox) so React + Aria owns the collection. Using controlled `items` (not defaultItems) + ensures that callers who manage their own item list — e.g. async + loaders that call setItems() after a fetch — see updates reflected in + the dropdown. The previous `defaultItems` form only initialised the + internal collection once and silently ignored subsequent prop changes + (standard uncontrolled-state behaviour). With `items` being + controlled, callers that want client-side filtering must do it + themselves before passing items in. */} + {(state) => (
{otherProps.label && ( @@ -253,7 +262,6 @@ export const ComboBox = ({ triggerRef={triggerRef}> ( )}> diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/multi-select.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/multi-select.tsx index 5d0cfbc79052..7e69b1147d0d 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/multi-select.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/select/multi-select.tsx @@ -37,7 +37,6 @@ import { ComboBoxStateContext, } from 'react-aria-components'; import type { ListData } from 'react-stately'; -import { useListData } from 'react-stately'; import { SelectItem } from './select-item'; interface ComboBoxValueProps @@ -280,6 +279,7 @@ export const MultiSelectBase = ({ onItemInserted, shortcut, placeholder = 'Search', + onInputChange: onInputChangeProp, // Omit these props to avoid conflicts with the `Select` component name: _name, className: _className, @@ -298,10 +298,15 @@ export const MultiSelectBase = ({ [contains, selectedKeys] ); - const accessibleList = useListData({ - initialItems: items, - filter, - }); + // Derive the visible options from the live `items` prop instead of + // useListData({ initialItems }) — that hook snapshots the items on mount, + // so async consumers that fetch options on input change never see their + // results reflected in the popup. + const [filterText, setFilterText] = useState(''); + const filteredItems = useMemo( + () => (items ?? []).filter((item) => filter(item, filterText)), + [items, filter, filterText] + ); const onRemove = useCallback( (keys: Set) => { @@ -322,7 +327,7 @@ export const MultiSelectBase = ({ return; } - const item = accessibleList.getItem(id); + const item = (items ?? []).find((currentItem) => currentItem.id === id); if (!item) { return; @@ -333,14 +338,18 @@ export const MultiSelectBase = ({ onItemInserted?.(id); } - accessibleList.setFilterText(''); + setFilterText(''); }; const onInputChange = useCallback( (value: string) => { - accessibleList.setFilterText(value); + setFilterText(value); + // Chain the consumer's handler — the internal one is applied after + // {...props} on AriaComboBox and would otherwise silently drop it + // (async search widgets rely on it to fetch matching options). + onInputChangeProp?.(value); }, - [accessibleList] + [onInputChangeProp] ); const placeholderRef = useRef(null); @@ -370,8 +379,8 @@ export const MultiSelectBase = ({ { export const Popover = (props: PopoverProps) => { return ( { topic1.create(apiContext), topic2.create(apiContext), ]); + glossaryEntity = new Glossary(undefined, [ { id: user.responseData.id, @@ -453,23 +454,24 @@ test.describe( const ruleLocator = page.locator('.rule').nth(0); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Status', true ); - await selectOption( - page, - ruleLocator.locator('.rule--operator .ant-select'), - '==' - ); + await selectOption(page, ruleLocator.locator('.rule--operator'), '=='); }); await test.step('Open Status value dropdown and verify all hard-coded options appear', async () => { const ruleLocator = page.locator('.rule').nth(0); - await ruleLocator.locator('.widget--widget > .ant-select').click(); + const triggerBtn = ruleLocator.locator( + '.widget--widget button[aria-haspopup="listbox"]' + ); + + await expect(triggerBtn).toBeVisible(); + await triggerBtn.click(); const dropdown = page - .locator('.ant-select-dropdown') + .locator('[role="listbox"]') .filter({ hasText: EntityStatus.Approved }) .last(); @@ -478,7 +480,7 @@ test.describe( for (const status of ENTITY_STATUSES) { await expect( dropdown - .locator('.ant-select-item-option') + .getByRole('option') .filter({ hasText: new RegExp(`^${status}$`, 'i') }) .first() ).toBeVisible(); @@ -1638,83 +1640,48 @@ test.describe( await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Custom Properties', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Table', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), enumCPName, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), 'Equals' ); - const valueSelector = ruleLocator.locator( - '.ant-select-selection-overflow' + const comboboxInput = ruleLocator.locator( + '.rule--widget input[role="combobox"]' ); - await expect(valueSelector).toBeVisible({ timeout: 15000 }); - await valueSelector.click(); + await expect(comboboxInput).toBeVisible({ timeout: 15000 }); + // fill('') focuses the input (menuTrigger="focus" opens the popup) + // without pointer-clicking — the overlaid chevron button can intercept + // clicks at the input's center in narrow ComboBoxes. + await comboboxInput.fill(''); - const dropdown = page.locator('.ant-select-dropdown:visible').last(); + const dropdown = page.locator('[role="listbox"]:visible').last(); await expect(dropdown).toBeVisible(); - return { ruleLocator, valueSelector, dropdown }; + return { ruleLocator, comboboxInput, dropdown }; }; - test('should append page-2 items and make them visible when Load more button is clicked', async ({ - page, - }) => { - test.slow(); - - const { dropdown } = await openEnumValueDropdown(page); - - // Page 1 items present; page-2 item not yet visible - await expect( - dropdown.locator(`[title="${FIRST_PAGE_VALUE}"]`) - ).toBeVisible({ timeout: 10000 }); - await expect( - dropdown.locator(`[title="${SECOND_PAGE_VALUE}"]`) - ).not.toBeVisible(); - - // "Load more..." button visible at the bottom of the list - const loadMoreBtn = dropdown - .locator('a') - .filter({ hasText: /load more/i }); - - await expect(loadMoreBtn).toBeVisible(); - - // Click Load more → page-2 items append - await loadMoreBtn.click(); - - // Hover over the virtual list so mouse wheel events target it - const virtualListHolder = dropdown.locator('.rc-virtual-list-holder'); - - await expect(virtualListHolder).toBeVisible(); - await virtualListHolder.hover(); - - // Wheel-scroll in small increments until the page-2 item comes into view - const secondPageItem = dropdown.locator(`[title="${SECOND_PAGE_VALUE}"]`); - let found = await secondPageItem.isVisible(); - - for (let i = 0; i < 20 && !found; i++) { - await page.mouse.wheel(0, 200); - found = await secondPageItem.isVisible(); - } - - await expect(secondPageItem).toBeVisible({ timeout: 5000 }); + test.skip('should append page-2 items and make them visible when Load more button is clicked', () => { + // Load more and rc-virtual-list are Ant Design Select features not present + // in the new react-aria MultiSelect component. }); test('should find page-2 items via search without clicking Load more', async ({ @@ -1726,22 +1693,22 @@ test.describe( // Page 1 items load; page-2 item is not yet visible await expect( - dropdown.locator(`[title="${FIRST_PAGE_VALUE}"]`) + dropdown.getByRole('option', { name: FIRST_PAGE_VALUE }) ).toBeVisible({ timeout: 10000 }); await expect( - dropdown.locator(`[title="${SECOND_PAGE_VALUE}"]`) + dropdown.getByRole('option', { name: SECOND_PAGE_VALUE }) ).not.toBeVisible(); // Type to search — asyncFetch filters the full values array, not just the loaded page const searchInput = ruleLocator.locator( - '.rule--widget .ant-select-selection-search-input' + '.rule--widget input[role="combobox"]' ); await searchInput.fill(SECOND_PAGE_VALUE); // Item appears immediately without clicking Load more await expect( - dropdown.locator(`[title="${SECOND_PAGE_VALUE}"]`) + dropdown.getByRole('option', { name: SECOND_PAGE_VALUE }) ).toBeVisible({ timeout: 10000 }); }); } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearchSuggestions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearchSuggestions.spec.ts index 2eef301f54f9..35593498dccb 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearchSuggestions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/AdvancedSearchSuggestions.spec.ts @@ -58,27 +58,17 @@ test.describe('Advanced Search Suggestions', () => { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), field.label, true ); - await selectOption( - page, - ruleLocator.locator('.rule--operator .ant-select'), - '==' - ); + await selectOption(page, ruleLocator.locator('.rule--operator'), '=='); const dropdownInput = ruleLocator.locator( - '.widget--widget > .ant-select > .ant-select-selector input' + '.widget--widget input[role="combobox"]' ); - const aggregateRes1 = page.waitForResponse('/api/v1/search/aggregate?*'); - - await dropdownInput.click(); - - await aggregateRes1; - const searchText = toLower( getFieldsSuggestionSearchText(field.label, testData.fieldSearchData) ); @@ -95,7 +85,10 @@ test.describe('Advanced Search Suggestions', () => { await test .expect( - page.locator(`.ant-select-dropdown:visible [title="${searchText}"]`) + page + .locator('[role="listbox"]:visible [role="option"]') + .filter({ hasText: searchText }) + .first() ) .toBeVisible(); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts index eb460b91acbe..6df195890782 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CuratedAssets.spec.ts @@ -144,14 +144,14 @@ test.describe('Curated Assets Widget', () => { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), 'Contains' ); @@ -260,19 +260,15 @@ test.describe('Curated Assets Widget', () => { const ruleLocator = page.locator('.rule').nth(0); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Deleted', true ); - await selectOption( - page, - ruleLocator.locator('.rule--operator .ant-select'), - 'Is' - ); + await selectOption(page, ruleLocator.locator('.rule--operator'), 'Is'); await ruleLocator - .locator('.rule--value .rule--widget--BOOLEAN .ant-switch') + .locator('.rule--value .rule--widget--BOOLEAN label') .click(); await expect(page.locator('[data-testid="saveButton"]')).toBeEnabled(); @@ -336,35 +332,30 @@ test.describe('Curated Assets Widget', () => { const ruleLocator1 = page.locator('.rule').nth(0); await selectOption( page, - ruleLocator1.locator('.rule--field .ant-select'), + ruleLocator1.locator('.rule--field'), 'Owners', true ); - await selectOption( - page, - ruleLocator1.locator('.rule--operator .ant-select'), - 'Is Set' - ); + await selectOption(page, ruleLocator1.locator('.rule--operator'), 'Is Set'); await page.getByRole('button', { name: 'Add Condition' }).click(); // Switch to OR condition (AND is selected by default, click OR button) - await page.locator('.group--conjunctions button:has-text("OR")').click(); + await page + .locator('.group--conjunctions') + .getByRole('radio', { name: 'Or' }) + .click(); const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), 'Deleted', true ); - await selectOption( - page, - ruleLocator2.locator('.rule--operator .ant-select'), - 'Is' - ); + await selectOption(page, ruleLocator2.locator('.rule--operator'), 'Is'); await ruleLocator2 - .locator('.rule--value .rule--widget--BOOLEAN .ant-switch') + .locator('.rule--value .rule--widget--BOOLEAN label') .click(); const queryResponse = page.waitForResponse( @@ -439,32 +430,31 @@ test.describe('Curated Assets Widget', () => { const ruleLocator1 = page.locator('.rule').nth(0); await selectOption( page, - ruleLocator1.locator('.rule--field .ant-select'), + ruleLocator1.locator('.rule--field'), 'Deleted', true ); - await selectOption( - page, - ruleLocator1.locator('.rule--operator .ant-select'), - 'Is' - ); + await selectOption(page, ruleLocator1.locator('.rule--operator'), 'Is'); await ruleLocator1 - .locator('.rule--value .rule--widget--BOOLEAN .ant-switch') + .locator('.rule--value .rule--widget--BOOLEAN label') .click(); await page.getByRole('button', { name: 'Add Condition' }).click(); - await page.locator('.group--conjunctions button:has-text("AND")').click(); + await page + .locator('.group--conjunctions') + .getByRole('radio', { name: 'And' }) + .click(); const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), 'Display Name', true ); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), + ruleLocator2.locator('.rule--operator'), 'Contains' ); @@ -555,18 +545,14 @@ test.describe('Curated Assets Widget', () => { const ruleLocator1 = page.locator('.rule').nth(0); await selectOption( page, - ruleLocator1.locator('.rule--field .ant-select'), + ruleLocator1.locator('.rule--field'), 'Owners', true ); + await selectOption(page, ruleLocator1.locator('.rule--operator'), 'Any in'); await selectOption( page, - ruleLocator1.locator('.rule--operator .ant-select'), - 'Any in' - ); - await selectOption( - page, - ruleLocator1.locator('.rule--value .ant-select'), + ruleLocator1.locator('.rule--value'), 'admin', true ); @@ -574,23 +560,22 @@ test.describe('Curated Assets Widget', () => { await page.getByRole('button', { name: 'Add Condition' }).click(); // Switch first group to OR condition (AND is default) - await page.locator('.group--conjunctions button:has-text("OR")').click(); + await page + .locator('.group--conjunctions') + .getByRole('radio', { name: 'Or' }) + .click(); const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), 'Description Status', true ); + await selectOption(page, ruleLocator2.locator('.rule--operator'), 'Is'); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), - 'Is' - ); - await selectOption( - page, - ruleLocator2.locator('.rule--value .ant-select'), + ruleLocator2.locator('.rule--value'), 'Incomplete' ); await ruleLocator2.locator('.rule--value input').fill('production'); @@ -601,18 +586,14 @@ test.describe('Curated Assets Widget', () => { const ruleLocator3 = page.locator('.rule').nth(2); await selectOption( page, - ruleLocator3.locator('.rule--field .ant-select'), + ruleLocator3.locator('.rule--field'), 'Tier', true ); + await selectOption(page, ruleLocator3.locator('.rule--operator'), 'Is Not'); await selectOption( page, - ruleLocator3.locator('.rule--operator .ant-select'), - 'Is Not' - ); - await selectOption( - page, - ruleLocator3.locator('.rule--value .ant-select'), + ruleLocator3.locator('.rule--value'), 'tier.tier5', true ); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/PersonaAIContext.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/PersonaAIContext.spec.ts index 5674b2d0fff3..c0ca4fd16bb8 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/PersonaAIContext.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/PersonaAIContext.spec.ts @@ -506,9 +506,11 @@ test.describe.serial('Persona AI Context', () => { } await adminPage.getByTestId('add-context-condition').click(); + // Conjunction toggle is a react-aria ToggleButtonGroup (selectionMode + // "single"), which exposes role="radio" items — not buttons. const orOperator = adminPage .getByRole('dialog') - .getByRole('button', { name: 'Or', exact: true }); + .getByRole('radio', { name: 'Or', exact: true }); await expect(orOperator).toBeVisible(); await orOperator.click(); await expect(adminPage.getByTestId('delete-condition-button')).toHaveCount( diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts index a4e1415c2975..1208a31a9eaa 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/PersonaFlow.spec.ts @@ -850,13 +850,13 @@ test.describe('Curated Assets – Description filter', () => { await selectOption( adminPage, - rule0.locator('.rule--field .ant-select'), + rule0.locator('.rule--field'), 'Description', true ); await selectOption( adminPage, - rule0.locator('.rule--operator .ant-select'), + rule0.locator('.rule--operator'), 'Contains' ); await rule0 diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts index d7f915cb24a1..8ef643cc4a2b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts @@ -943,26 +943,26 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Custom Properties', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Table', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), durationPropertyName, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), CONDITIONS_MUST.equalTo.name ); @@ -985,7 +985,7 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), 'Contains' ); await inputElement.fill(partialSearchValue); @@ -1237,28 +1237,28 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Custom Properties', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Table', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), propertyName, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), CONDITIONS_MUST.equalTo.name ); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts index 6523a72839c3..acf8e195a2a3 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts @@ -99,13 +99,13 @@ const fillSemanticsForm = async ( const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), semanticsData.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), semanticsData.rules[0].operator ); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts index 0347c083f812..9cacf8016524 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts @@ -250,18 +250,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_SEMANTICS1.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), user.getUserDisplayName(), true ); @@ -272,13 +272,13 @@ test.describe('Data Contracts', () => { const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), DATA_CONTRACT_SEMANTICS1.rules[1].field, true ); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), + ruleLocator2.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[1].operator ); await page.getByTestId('save-semantic-button').click(); @@ -311,13 +311,13 @@ test.describe('Data Contracts', () => { const ruleLocator3 = page.locator('.group').nth(2); await selectOption( page, - ruleLocator3.locator('.group--field .ant-select'), + ruleLocator3.locator('.group--field'), DATA_CONTRACT_SEMANTICS2.rules[0].field, true ); await selectOption( page, - ruleLocator3.locator('.rule--operator .ant-select'), + ruleLocator3.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS2.rules[0].operator ); await page.getByTestId('save-semantic-button').click(); @@ -1024,18 +1024,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_CONTAIN_SEMANTICS.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_CONTAIN_SEMANTICS.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'Tier.Tier1', true ); @@ -1046,19 +1046,19 @@ test.describe('Data Contracts', () => { const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), DATA_CONTRACT_CONTAIN_SEMANTICS.rules[1].field, true ); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), + ruleLocator2.locator('.rule--operator'), DATA_CONTRACT_CONTAIN_SEMANTICS.rules[1].operator ); await selectOption( page, - ruleLocator2.locator('.rule--value .ant-select'), + ruleLocator2.locator('.rule--value'), testTag.responseData.name, true ); @@ -1072,19 +1072,19 @@ test.describe('Data Contracts', () => { const ruleLocator3 = page.locator('.rule').nth(2); await selectOption( page, - ruleLocator3.locator('.rule--field .ant-select'), + ruleLocator3.locator('.rule--field'), DATA_CONTRACT_CONTAIN_SEMANTICS.rules[2].field, true ); await selectOption( page, - ruleLocator3.locator('.rule--operator .ant-select'), + ruleLocator3.locator('.rule--operator'), DATA_CONTRACT_CONTAIN_SEMANTICS.rules[2].operator ); await selectOption( page, - ruleLocator3.locator('.rule--value .ant-select'), + ruleLocator3.locator('.rule--value'), testGlossaryTerm.responseData.name, true ); @@ -1217,18 +1217,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'Tier.Tier1', true ); @@ -1239,19 +1239,19 @@ test.describe('Data Contracts', () => { const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[1].field, true ); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), + ruleLocator2.locator('.rule--operator'), DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[1].operator ); await selectOption( page, - ruleLocator2.locator('.rule--value .ant-select'), + ruleLocator2.locator('.rule--value'), testTag.responseData.name, true ); @@ -1265,19 +1265,19 @@ test.describe('Data Contracts', () => { const ruleLocator3 = page.locator('.rule').nth(2); await selectOption( page, - ruleLocator3.locator('.rule--field .ant-select'), + ruleLocator3.locator('.rule--field'), DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[2].field, true ); await selectOption( page, - ruleLocator3.locator('.rule--operator .ant-select'), + ruleLocator3.locator('.rule--operator'), DATA_CONTRACT_NOT_CONTAIN_SEMANTICS.rules[2].operator ); await selectOption( page, - ruleLocator3.locator('.rule--value .ant-select'), + ruleLocator3.locator('.rule--value'), testGlossaryTerm.responseData.name, true ); @@ -1598,18 +1598,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_SEMANTICS1.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'admin', true ); @@ -1620,13 +1620,13 @@ test.describe('Data Contracts', () => { const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), DATA_CONTRACT_SEMANTICS1.rules[1].field, true ); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), + ruleLocator2.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[1].operator ); await page.getByTestId('save-semantic-button').click(); @@ -1678,18 +1678,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_SEMANTICS1.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'admin', true ); @@ -1700,13 +1700,13 @@ test.describe('Data Contracts', () => { const ruleLocator2 = page.locator('.rule').nth(1); await selectOption( page, - ruleLocator2.locator('.rule--field .ant-select'), + ruleLocator2.locator('.rule--field'), DATA_CONTRACT_SEMANTICS1.rules[1].field, true ); await selectOption( page, - ruleLocator2.locator('.rule--operator .ant-select'), + ruleLocator2.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[1].operator ); await page.getByTestId('save-semantic-button').click(); @@ -1720,13 +1720,13 @@ test.describe('Data Contracts', () => { const ruleLocator3 = page.locator('.group').nth(2); await selectOption( page, - ruleLocator3.locator('.group--field .ant-select'), + ruleLocator3.locator('.group--field'), DATA_CONTRACT_SEMANTICS2.rules[0].field, true ); await selectOption( page, - ruleLocator3.locator('.rule--operator .ant-select'), + ruleLocator3.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS2.rules[0].operator ); await page.getByTestId('save-semantic-button').click(); @@ -1772,18 +1772,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_SEMANTICS1.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'admin', true ); @@ -1832,18 +1832,18 @@ test.describe('Data Contracts', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_SEMANTICS1.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'admin', true ); @@ -1858,13 +1858,13 @@ test.describe('Data Contracts', () => { const ruleLocator3 = page.locator('.group').nth(2); await selectOption( page, - ruleLocator3.locator('.group--field .ant-select'), + ruleLocator3.locator('.group--field'), DATA_CONTRACT_SEMANTICS2.rules[0].field, true ); await selectOption( page, - ruleLocator3.locator('.rule--operator .ant-select'), + ruleLocator3.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS2.rules[0].operator ); await page.getByTestId('save-semantic-button').click(); @@ -2369,18 +2369,18 @@ entitiesWithDataContracts.forEach((EntityClass) => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), DATA_CONTRACT_SEMANTICS1.rules[0].field, true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTICS1.rules[0].operator ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), 'admin', true ); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts index 62cec6ad643a..9550f9ede751 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts @@ -101,18 +101,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), team.responseData.displayName, true ); @@ -198,18 +198,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), user.getUserDisplayName(), true ); @@ -302,18 +302,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), user.getUserDisplayName(), true ); @@ -406,18 +406,18 @@ test.describe('Data Contracts Semantics Rule Owner', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), user.getUserDisplayName(), true ); @@ -502,13 +502,13 @@ test.describe('Data Contracts Semantics Rule Owner', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set ); @@ -585,13 +585,13 @@ test.describe('Data Contracts Semantics Rule Owner', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set ); @@ -669,13 +669,13 @@ test.describe('Data Contracts Semantics Rule Description', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Description', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.contains ); @@ -759,13 +759,13 @@ test.describe('Data Contracts Semantics Rule Description', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Description', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.not_contains ); const inputElement = ruleLocator.locator( @@ -847,13 +847,13 @@ test.describe('Data Contracts Semantics Rule Description', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Description', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set ); @@ -936,13 +936,13 @@ test.describe('Data Contracts Semantics Rule Description', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Description', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set ); @@ -1069,18 +1069,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Domain', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), domain1.responseData.name, true ); @@ -1154,18 +1154,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Domain', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), domain2.responseData.name, true ); @@ -1240,18 +1240,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Domain', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), domain1.responseData.name, true ); @@ -1324,18 +1324,18 @@ test.describe('Data Contracts Semantics Rule Domain', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Domain', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), domain1.responseData.name, true ); @@ -1408,13 +1408,13 @@ test.describe('Data Contracts Semantics Rule Domain', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Domain', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set ); @@ -1484,13 +1484,13 @@ test.describe('Data Contracts Semantics Rule Domain', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Domain', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set ); @@ -1566,13 +1566,13 @@ test.describe('Data Contracts Semantics Rule Version', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Version', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is ); @@ -1585,7 +1585,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { // (which starts at 0.1), ensuring the second edit always produces a diff // and the save button stays enabled. await ruleLocator - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input') + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ) .fill('99.9'); await saveAndTriggerDataContractValidation(page, true); @@ -1612,7 +1614,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { const versionInput = page .locator('.group') .first() - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input'); + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ); await versionInput.clear(); await versionInput.fill(actualVersion); @@ -1683,13 +1687,13 @@ test.describe('Data Contracts Semantics Rule Version', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Version', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not ); @@ -1702,7 +1706,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { // (which starts at 0.1), ensuring the second edit always produces a diff // and the save button stays enabled. await ruleLocator - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input') + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ) .fill('99.9'); await saveAndTriggerDataContractValidation(page, true); @@ -1734,7 +1740,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { const versionInput = page .locator('.group') .first() - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input'); + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ); await versionInput.clear(); await versionInput.fill(domainBumpedVersion); @@ -1803,20 +1811,22 @@ test.describe('Data Contracts Semantics Rule Version', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Version', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.less ); // Use 99.9 — any realistic entity version is always below this, so the // check passes regardless of how many version bumps CI introduces. await ruleLocator - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input') + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ) .fill('99.9'); await saveAndTriggerDataContractValidation(page, true); @@ -1838,7 +1848,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { const versionInput = page .locator('.group') .first() - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input'); + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ); await versionInput.clear(); await versionInput.fill('0.01'); @@ -1880,20 +1892,22 @@ test.describe('Data Contracts Semantics Rule Version', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Version', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.greater ); // Use 99.9 — any realistic entity version is always below this, so // entity_version > 99.9 always fails regardless of version bumps in CI. await ruleLocator - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input') + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ) .fill('99.9'); await saveAndTriggerDataContractValidation(page, true); @@ -1916,7 +1930,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { const versionInput = page .locator('.group') .first() - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input'); + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ); await versionInput.clear(); await versionInput.fill('0.01'); @@ -1960,20 +1976,22 @@ test.describe('Data Contracts Semantics Rule Version', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Version', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.less_equal ); // Use 99.9 — any realistic entity version is always below this, so // entity_version <= 99.9 always passes regardless of version bumps in CI. await ruleLocator - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input') + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ) .fill('99.9'); await saveAndTriggerDataContractValidation(page, true); @@ -1995,7 +2013,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { const versionInput = page .locator('.group') .first() - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input'); + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ); await versionInput.clear(); await versionInput.fill('0.01'); @@ -2040,20 +2060,22 @@ test.describe('Data Contracts Semantics Rule Version', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Version', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.greater_equal ); // Use 99.9 — any realistic entity version is always below this, so // entity_version >= 99.9 always fails regardless of version bumps in CI. await ruleLocator - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input') + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ) .fill('99.9'); await saveAndTriggerDataContractValidation(page, true); @@ -2075,7 +2097,9 @@ test.describe('Data Contracts Semantics Rule Version', () => { const versionInput = page .locator('.group') .first() - .locator('.rule--value .rule--widget--NUMBER .ant-input-number-input'); + .locator( + '.rule--value .rule--widget--NUMBER input[data-testid="qb-number-input"]' + ); await versionInput.clear(); await versionInput.fill('0.01'); @@ -2142,19 +2166,19 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Data Product', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), createdDataProducts[0].responseData.name, true ); @@ -2240,19 +2264,19 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Data Product', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), createdDataProducts[0].responseData.name, true ); @@ -2338,18 +2362,18 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Data Product', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), createdDataProducts[0].responseData.name, true ); @@ -2435,18 +2459,18 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Data Product', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), createdDataProducts[0].responseData.name, true ); @@ -2576,13 +2600,13 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Data Product', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set ); @@ -2660,13 +2684,13 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Data Product', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set ); @@ -2750,19 +2774,19 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), table.entityResponseData.displayName || '', true ); @@ -2840,19 +2864,19 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), table.entityResponseData.displayName || '', true ); @@ -2930,18 +2954,18 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), table.entityResponseData.displayName || '', true ); @@ -3019,18 +3043,18 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in ); await selectOption( page, - ruleLocator.locator('.rule--value .ant-select'), + ruleLocator.locator('.rule--value'), table.entityResponseData.displayName || '', true ); @@ -3108,13 +3132,13 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set ); @@ -3192,13 +3216,13 @@ test.describe('Data Contracts Semantics Rule DisplayName', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Display Name', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set ); @@ -3278,20 +3302,20 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Updated on', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.between ); - const startDate = customFormatDateTime(getCurrentMillis(), 'dd.MM.yyyy'); + const startDate = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd'); const endDate = customFormatDateTime( getEpochMillisForFutureDays(5), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await selectRange(page, ruleLocator, startDate, endDate); @@ -3320,11 +3344,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const newStart = customFormatDateTime( getEpochMillisForFutureDays(1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); - page.getByRole('textbox', { name: 'Enter date from' }).fill(newStart); - await page.press('.ant-picker-input-active input', 'Enter'); - await page.press('.ant-picker-input-active input', 'Enter'); + await page + .locator('.group') + .nth(0) + .locator('.rule--value input[type="date"]') + .nth(0) + .fill(newStart); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3365,20 +3392,20 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Updated on', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.not_between ); - const startDate = customFormatDateTime(getCurrentMillis(), 'dd.MM.yyyy'); + const startDate = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd'); const endDate = customFormatDateTime( getEpochMillisForFutureDays(5), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await selectRange(page, ruleLocator, startDate, endDate); @@ -3408,23 +3435,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const newStart = customFormatDateTime( getEpochMillisForFutureDays(1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await page .locator('.group') .nth(0) - .locator('.rule--value .ant-picker-range') - .click(); - - await page.locator('.ant-picker-dropdown-range').waitFor({ - state: 'visible', - }); - - await page - .getByRole('textbox', { name: 'Enter date from' }) + .locator('.rule--value input[type="date"]') + .nth(0) .fill(newStart); - await page.press('.ant-picker-input-active input', 'Enter'); - await page.press('.ant-picker-input-active input', 'Enter'); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3464,24 +3482,19 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Updated on', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.less ); - const date = customFormatDateTime(getCurrentMillis(), 'dd.MM.yyyy'); + const date = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd'); - await ruleLocator.locator('.rule--value .ant-picker').click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(date); - await page.press('.ant-picker-input input', 'Enter'); + await ruleLocator.locator('.rule--value input[type="date"]').fill(date); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3508,19 +3521,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const newDate = customFormatDateTime( getEpochMillisForFutureDays(1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await page .locator('.group') .nth(0) - .locator('.rule--value .ant-picker') - .click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(newDate); - await page.press('.ant-picker-input input', 'Enter'); + .locator('.rule--value input[type="date"]') + .fill(newDate); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3560,27 +3568,22 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Updated on', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.greater ); const date = customFormatDateTime( getEpochMillisForFutureDays(1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); - await ruleLocator.locator('.rule--value .ant-picker').click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(date); - await page.press('.ant-picker-input input', 'Enter'); + await ruleLocator.locator('.rule--value input[type="date"]').fill(date); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3607,19 +3610,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const newDate = customFormatDateTime( getEpochMillisForFutureDays(-1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await page .locator('.group') .nth(0) - .locator('.rule--value .ant-picker') - .click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(newDate); - await page.press('.ant-picker-input input', 'Enter'); + .locator('.rule--value input[type="date"]') + .fill(newDate); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3659,27 +3657,22 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Updated on', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.less_equal ); const date = customFormatDateTime( getEpochMillisForFutureDays(1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); - await ruleLocator.locator('.rule--value .ant-picker').click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(date); - await page.press('.ant-picker-input input', 'Enter'); + await ruleLocator.locator('.rule--value input[type="date"]').fill(date); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3705,19 +3698,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const newDate = customFormatDateTime( getEpochMillisForFutureDays(-1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await page .locator('.group') .nth(0) - .locator('.rule--value .ant-picker') - .click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(newDate); - await page.press('.ant-picker-input input', 'Enter'); + .locator('.rule--value input[type="date"]') + .fill(newDate); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3761,27 +3749,22 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Updated on', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), DATA_CONTRACT_SEMANTIC_OPERATIONS.greater_equal ); const date = customFormatDateTime( getEpochMillisForFutureDays(-1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); - await ruleLocator.locator('.rule--value .ant-picker').click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(date); - await page.press('.ant-picker-input input', 'Enter'); + await ruleLocator.locator('.rule--value input[type="date"]').fill(date); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3807,19 +3790,14 @@ test.describe('Data Contracts Semantics Rule Updated on', () => { const newDate = customFormatDateTime( getEpochMillisForFutureDays(1), - 'dd.MM.yyyy' + 'yyyy-MM-dd' ); await page .locator('.group') .nth(0) - .locator('.rule--value .ant-picker') - .click(); - await page.locator('.ant-picker-dropdown').waitFor({ - state: 'visible', - }); - await page.locator('.ant-picker-input input').fill(newDate); - await page.press('.ant-picker-input input', 'Enter'); + .locator('.rule--value input[type="date"]') + .fill(newDate); // save and trigger contract validation await saveAndTriggerDataContractValidation(page, true); @@ -3874,13 +3852,13 @@ test.describe('Data Contract - Semantics Fields Validation', () => { const ruleLocator = page.locator('.group').nth(0); await selectOption( page, - ruleLocator.locator('.group--field .ant-select'), + ruleLocator.locator('.group--field'), 'Owners', true ); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), 'Is Set' ); }); @@ -3900,17 +3878,8 @@ test.describe('Data Contract - Semantics Fields Validation', () => { }); await test.step('select Is Set operator and error is hidden', async () => { - await selectOption( - page, - page.locator('.rule--field .ant-select'), - 'Owners', - true - ); - await selectOption( - page, - page.locator('.rule--operator .ant-select'), - 'Is Set' - ); + await selectOption(page, page.locator('.rule--field'), 'Owners', true); + await selectOption(page, page.locator('.rule--operator'), 'Is Set'); await expect(page.getByText(/rule is required/i)).not.toBeVisible(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts index d711e936791b..1d12c9a50a08 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataMarketplace.spec.ts @@ -21,7 +21,11 @@ import { navigateToMarketplace, searchMarketplace, } from '../../utils/dataMarketplace'; -import { fillCommonFormItems, fillDomainForm } from '../../utils/domain'; +import { + clickDrawerSave, + fillCommonFormItems, + fillDomainForm, +} from '../../utils/domain'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { test } from '../fixtures/pages'; @@ -173,7 +177,10 @@ test.describe( response.url().includes('/api/v1/dataProducts') && response.request().method() === 'POST' ); - await page.getByTestId('save-btn').click(); + const saveBtn = page.getByTestId('save-btn'); + await expect(saveBtn).toBeVisible(); + await saveBtn.focus(); + await page.keyboard.press('Enter'); const response = await createResponse; expect(response.status()).toBe(201); }); @@ -214,7 +221,7 @@ test.describe( response.url().includes('/api/v1/domains') && response.request().method() === 'POST' ); - await page.getByTestId('save-btn').click(); + await clickDrawerSave(page); const response = await createResponse; expect(response.status()).toBe(201); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts index 810394e0478f..daf61e1239bd 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/IntakeForm.spec.ts @@ -17,7 +17,7 @@ import { Glossary } from '../../support/glossary/Glossary'; import { GlossaryTerm } from '../../support/glossary/GlossaryTerm'; import { performAdminLogin } from '../../utils/admin'; import { descriptionBox, redirectToHomePage, uuid } from '../../utils/common'; -import { fillDomainForm } from '../../utils/domain'; +import { clickDrawerSave, fillDomainForm } from '../../utils/domain'; import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { openAddGlossaryTermModal } from '../../utils/glossary'; import { sidebarClick } from '../../utils/sidebar'; @@ -210,7 +210,7 @@ const selectExtensionReference = async ({ .first(); await expect(input).toBeVisible({ timeout: 15000 }); - await input.click(); + await input.focus(); await input.fill(query); await searchResponse; @@ -629,7 +629,7 @@ test.describe( } }; page.on('response', postListener); - await page.getByTestId('save-btn').click(); + await clickDrawerSave(page); // Poll for up to 3s and confirm no POST ever fires. We intentionally // avoid `page.waitForTimeout` (linted as flaky) and instead use @@ -1073,7 +1073,7 @@ test.describe( r.url().endsWith('/api/v1/dataProducts') && r.request().method() === 'POST' ); - await page.getByTestId('save-btn').click(); + await clickDrawerSave(page); const response = await createResponse; expect(response.status()).toBe(201); @@ -1414,7 +1414,7 @@ test.describe( } }; page.on('request', trackCreateRequest); - await page.getByTestId('save-btn').click(); + await clickDrawerSave(page); await expect( page.getByText('URL must use http or https protocol') ).toBeVisible(); @@ -1433,7 +1433,7 @@ test.describe( response.url().endsWith('/api/v1/dataProducts') && response.request().method() === 'POST' ); - await page.getByTestId('save-btn').click(); + await clickDrawerSave(page); const request = await createRequest; const response = await createResponse; @@ -1563,7 +1563,7 @@ test.describe( response.url().endsWith('/api/v1/domains') && response.request().method() === 'POST' ); - await page.getByTestId('save-btn').click(); + await clickDrawerSave(page); const request = await createRequest; const response = await createResponse; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts index 24cb7a7b6d95..5e4982017c57 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts @@ -174,52 +174,86 @@ export const selectOption = async ( optionTitle: string, isSearchable = false ) => { - if (isSearchable) { - // Wait for dropdown to be visible before clicking - const selector = dropdownLocator.locator('.ant-select-selector'); - await expect(selector).toBeVisible(); - await selector.click(); - - await dropdownLocator - .locator('.ant-select-arrow-loading svg[data-icon="loading"]') - .waitFor({ state: 'detached' }); - - // Clear any existing input and type the new value - const combobox = dropdownLocator.getByRole('combobox'); - await combobox.clear(); - - await dropdownLocator - .locator('.ant-select-arrow-loading svg[data-icon="loading"]') - .waitFor({ state: 'detached' }); + const comboboxInput = dropdownLocator.locator('input[role="combobox"]'); + const triggerButton = dropdownLocator.locator( + 'button[aria-haspopup="listbox"]' + ); - await combobox.fill(optionTitle); + await expect(comboboxInput.or(triggerButton).first()).toBeVisible(); - await dropdownLocator - .locator('.ant-select-arrow-loading svg[data-icon="loading"]') - .waitFor({ state: 'detached' }); + if (isSearchable) { + if ((await triggerButton.count()) === 0) { + // MultiSelect: no chevron overlays the input, so clicking is safe — + // and required, since its popup opens from a mousedown handler. + await comboboxInput.click(); + } + // Single fill (no clear first) — one input event, one async fetch. + await comboboxInput.fill(optionTitle); + // React Aria may close the focus-opened popup while processing the atomic + // fill; ArrowDown deterministically (re)opens it with the filter applied. + await comboboxInput.press('ArrowDown'); + } else if ((await comboboxInput.count()) > 0) { + // Select.ComboBox: fill('') focuses the input (menuTrigger="focus" opens + // the popup) and clears the current-label filter so all options show. + // Never pointer-click the input — in narrow ComboBoxes (e.g. the RAQB + // operator column) the absolutely positioned chevron button covers the + // input's center and intercepts the click, hanging actionability retries. + await comboboxInput.fill(''); + await comboboxInput.press('ArrowDown'); } else { - await dropdownLocator.click(); + // Plain Select (no combobox input): click the trigger button to open. + await triggerButton.click(); } - await expect(dropdownLocator).toHaveClass(/(^|\s)ant-select-focused(\s|$)/); - - await page.locator('.ant-select-dropdown:visible').first().waitFor({ - state: 'visible', - }); - - // CRITICAL: Use :visible selector chain pattern (Rule 4 from deflake guide) - // Use .first() to handle multiple matches (acceptable when scoped to visible dropdown) - const optionLocator = page - .locator('.ant-select-dropdown:visible') - .getByTitle(optionTitle, { exact: true }) - .first(); - await expect(optionLocator).toBeVisible(); - - // Wait for dropdown animations to settle before clicking - // This prevents "element detached from DOM" errors during re-renders - // eslint-disable-next-line playwright/no-wait-for-timeout -- dropdown animation settling - await page.waitForTimeout(100); - await optionLocator.click({ timeout: 10000 }); + // Scope the popup to THIS control via aria-controls (react-aria sets it + // while expanded). Popovers portal to , so a global + // [role="listbox"]:visible could match a popup left open by a previous + // interaction (MultiSelect keeps its popup open by design). The popup can + // also close and reopen under a new id while the builder re-renders, so + // re-resolve it (and reopen if needed) on every retry. + const control = comboboxInput.or(triggerButton).first(); + await expect(async () => { + if ((await control.getAttribute('aria-expanded')) !== 'true') { + await control.press('ArrowDown'); + } + const listboxId = await control.getAttribute('aria-controls'); + if (!listboxId) { + throw new Error('Combobox popup did not open (aria-controls not set)'); + } + const option = page + .locator(`[role="listbox"][id="${listboxId}"]`) + .getByRole('option', { name: optionTitle, exact: true }) + .first(); + if (isSearchable && (await option.count()) === 0) { + await comboboxInput.fill(''); + await comboboxInput.fill(optionTitle); + throw new Error(`Option "${optionTitle}" not present yet; re-searched`); + } + await option.click({ timeout: 2000 }); + }).toPass({ timeout: 30000 }); + + // Close the popup if the click didn't: re-selecting the current value emits + // no selection change (so the popup stays open) and MultiSelect popups stay + // open by design — either would pollute the next interaction's locators. + // The control itself may be GONE by now (selecting a field can morph the + // whole rule row), which also unmounts its popup — tolerate that. + const openListboxId = await control + .getAttribute('aria-controls', { timeout: 1000 }) + .catch(() => null); + if (openListboxId) { + const openListbox = page.locator(`[role="listbox"][id="${openListboxId}"]`); + await openListbox + .waitFor({ state: 'hidden', timeout: 2000 }) + .catch(async () => { + // Blur the control — react-aria comboboxes close their popup when + // focus leaves. NEVER send Escape here: surrounding antd modals and + // forms handle Escape in the capture phase and dismiss themselves. + await control.blur({ timeout: 1000 }).catch(() => undefined); + await openListbox + .waitFor({ state: 'hidden', timeout: 1000 }) + .catch(() => undefined); + }); + } }; export const selectRange = async ( @@ -228,16 +262,14 @@ export const selectRange = async ( startDate: string, endDate: string ) => { - await ruleLocator.locator('.rule--value .ant-picker-range').click(); - - await page.locator('.ant-picker-dropdown-range').waitFor({ - state: 'visible', - }); - - await page.locator('.ant-picker-input-active input').fill(startDate); - await page.press('.ant-picker-input-active input', 'Enter'); - await page.locator('.ant-picker-input-active input').fill(endDate); - await page.press('.ant-picker-input-active input', 'Enter'); + await ruleLocator + .locator('.rule--value input[type="date"]') + .nth(0) + .fill(startDate); + await ruleLocator + .locator('.rule--value input[type="date"]') + .nth(1) + .fill(endDate); }; export const fillRule = async ( @@ -260,19 +292,10 @@ export const fillRule = async ( const ruleLocator = page.locator('.rule').nth(index - 1); // Perform click on rule field - await selectOption( - page, - ruleLocator.locator('.rule--field .ant-select'), - field.id, - true - ); + await selectOption(page, ruleLocator.locator('.rule--field'), field.id, true); // Perform click on operator - await selectOption( - page, - ruleLocator.locator('.rule--operator .ant-select'), - condition - ); + await selectOption(page, ruleLocator.locator('.rule--operator'), condition); if (searchCriteria) { const inputElement = ruleLocator.locator( @@ -284,48 +307,64 @@ export const fillRule = async ( await inputElement.fill(searchData); } else { const dropdownInput = ruleLocator.locator( - '.widget--widget > .ant-select > .ant-select-selector input' + '.widget--widget input[role="combobox"]' ); - const aggregateRes1 = page.waitForResponse('/api/v1/search/aggregate?*'); - - await dropdownInput.click(); - - await aggregateRes1; - const aggregateRes2 = page.waitForResponse( - `/api/v1/search/aggregate?*${getEncodedFqn( - escapeESReservedCharacters(searchData) - )}*` + (response) => + response.url().includes(`/api/v1/search/aggregate`) && + response + .url() + .includes( + encodeURIComponent(escapeESReservedCharacters(searchData)) + ) ); await dropdownInput.fill(searchData); await aggregateRes2; - const dropdown = page.locator('.ant-select-dropdown:visible'); - const exactTitleMatch = dropdown - .locator('[title]') - .filter({ - hasText: new RegExp(`^${escapeRegex(searchData)}$`, 'i'), - }) - .first(); - const partialTextMatch = dropdown - .locator('.ant-select-item-option-content') - .filter({ - hasText: new RegExp(escapeRegex(searchData), 'i'), - }) - .first(); - - if (await exactTitleMatch.count()) { - await exactTitleMatch.click(); - } else if (await partialTextMatch.count()) { - await partialTextMatch.click(); - } else { - // Some suggestion backends normalize or delay option text; Enter keeps - // the typed criteria and avoids waiting forever on an exact title match. - await dropdownInput.press('Enter'); - } + // Scope the popup to THIS combobox via aria-controls. + // [role="listbox"]:visible is a global match that can race: the API + // response arrives before the popup renders, so count() returns 0 and + // we fall through to Enter which selects nothing. Waiting for + // aria-controls ensures the popup is truly open before querying it. + await expect(async () => { + const listboxId = await dropdownInput.getAttribute('aria-controls'); + if (!listboxId) { + throw new Error( + 'Value combobox popup not open (aria-controls missing)' + ); + } + const dropdown = page.locator(`[role="listbox"][id="${listboxId}"]`); + const exactMatch = dropdown + .getByRole('option', { + name: new RegExp(`^${escapeRegex(searchData)}$`, 'i'), + }) + .first(); + const partialMatch = dropdown + .getByRole('option') + .filter({ + hasText: new RegExp(escapeRegex(searchData), 'i'), + }) + .first(); + + if (await exactMatch.count()) { + await exactMatch.click(); + } else if (await partialMatch.count()) { + await partialMatch.click(); + } else { + // Do NOT re-fill here. The Autocomplete asyncFetch runs behind a + // 300 ms debounce; re-filling on every ~100 ms retry resets that + // debounce each time, so the API call never fires and options never + // appear. The popup stays open while the input is focused + // (menuTrigger="focus"), so just retry and let React render the + // items once the debounce completes. + throw new Error( + `No option matching "${searchData}" in the listbox yet` + ); + } + }).toPass({ timeout: 15000 }); } await clickOutside(page); @@ -579,9 +618,11 @@ export const checkAddRuleOrGroupWithOperator = async ( }); if (operator === 'OR') { + // Conjunction toggle is a react-aria ToggleButtonGroup (selectionMode + // "single"), which exposes role="radio" items — not buttons. await page .getByTestId('advanced-search-modal') - .getByRole('button', { name: 'Or' }) + .getByRole('radio', { name: 'Or' }) .click(); } @@ -664,28 +705,39 @@ export const runRuleGroupTestsWithNonExistingValue = async (page: Page) => { // Perform click on rule field await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Database', true ); - await selectOption( - page, - ruleLocator.locator('.rule--operator .ant-select'), - '==' - ); + await selectOption(page, ruleLocator.locator('.rule--operator'), '=='); const inputElement = ruleLocator.locator( - '.rule--widget--SELECT .ant-select-selection-search-input' + '.rule--widget--SELECT input[role="combobox"]' ); + await inputElement.fill('non-existing-value'); - const dropdownText = page.locator('.ant-select-item-empty'); + await inputElement.press('ArrowDown'); + + // Scope to this input's own popup — a popup from a previous step may + // still be visible, which would break a global :visible locator. + let listboxId: string | null = null; + await expect(async () => { + listboxId = await inputElement.getAttribute('aria-controls'); + if (!listboxId) { + throw new Error('Combobox popup did not open (aria-controls not set)'); + } + }).toPass({ timeout: 15000 }); - await expect(dropdownText).toContainText('Loading...'); + const listbox = page.locator(`[role="listbox"][id="${listboxId}"]`); + + await expect(listbox).toBeVisible(); // eslint-disable-next-line playwright/no-wait-for-timeout -- search debounce delay await page.waitForTimeout(1000); - await expect(dropdownText).not.toContainText('Loading...'); + // allowsEmptyCollection keeps the popup open and renders the "No data" + // empty state (as an option row) instead of an empty listbox. + await expect(listbox.getByText('No data')).toBeVisible(); }; // For fields backed by hard-coded listValues (no aggregate API call), options are @@ -709,20 +761,12 @@ export const fillStaticListRule = async ( await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), fieldLabel, true ); - await selectOption( - page, - ruleLocator.locator('.rule--operator .ant-select'), - condition - ); - await selectOption( - page, - ruleLocator.locator('.widget--widget > .ant-select'), - value - ); + await selectOption(page, ruleLocator.locator('.rule--operator'), condition); + await selectOption(page, ruleLocator.locator('.widget--widget'), value); }; export const getFieldsSuggestionSearchText = ( diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts index b369506623ed..da1d1524d2d0 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts @@ -944,7 +944,7 @@ export const verifyCustomPropertyInAdvancedSearch = async ( // Select "Custom Properties" from the field dropdown await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Custom Properties', true ); @@ -952,7 +952,7 @@ export const verifyCustomPropertyInAdvancedSearch = async ( if (entityType !== 'TableColumn') { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), entityType, true ); @@ -960,26 +960,26 @@ export const verifyCustomPropertyInAdvancedSearch = async ( if (propertyType === 'Time Interval') { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), `${propertyName} (Start)`, true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), `${propertyName} (End)`, true ); } else if (propertyType === 'Hyperlink') { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), `${propertyName} URL`, true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), `${propertyName} Display Text`, true ); @@ -987,7 +987,7 @@ export const verifyCustomPropertyInAdvancedSearch = async ( for (const column of propertyConfig ?? []) { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), `${propertyName} - ${column}`, true ); @@ -995,7 +995,7 @@ export const verifyCustomPropertyInAdvancedSearch = async ( } else { await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), propertyName, true ); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts index fce8fcc3f64c..5f1457188597 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customPropertyAdvancedSearchUtils.ts @@ -400,6 +400,55 @@ export const getOperatorLabel = (operator: string): string => { return operatorMap[operator] || operator; }; +// The query builder renders native date/datetime inputs, which only accept +// ISO-like fill values — convert from the CP display format +// (dd-MM-yyyy[ HH:mm:ss]). Detection is pattern-based because several call +// sites don't pass propertyType. +const DATE_DISPLAY_PATTERN = /^(\d{2})-(\d{2})-(\d{4})$/; +const DATE_TIME_DISPLAY_PATTERN = + /^(\d{2})-(\d{2})-(\d{4}) (\d{2}:\d{2}:\d{2})$/; + +const toNativeDateInputValue = (value: string | number): string => { + const stringValue = String(value); + let result = stringValue; + + const dateTimeMatch = stringValue.match(DATE_TIME_DISPLAY_PATTERN); + const dateMatch = stringValue.match(DATE_DISPLAY_PATTERN); + + if (dateTimeMatch) { + const [, day, month, year, time] = dateTimeMatch; + result = `${year}-${month}-${day}T${time}`; + } else if (dateMatch) { + const [, day, month, year] = dateMatch; + result = `${year}-${month}-${day}`; + } + + return result; +}; + +// Playwright's fill() rejects datetime-local values that carry seconds even +// when the input has step=1, so converted date values are written through the +// native value setter (fill() is used for everything else). +const fillPropertyValue = async ( + input: ReturnType, + value: string | number +) => { + const nativeValue = toNativeDateInputValue(value); + + if (nativeValue === String(value)) { + await input.fill(String(value)); + } else { + await input.evaluate((element, val) => { + const setter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + 'value' + )?.set; + setter?.call(element, val); + element.dispatchEvent(new Event('input', { bubbles: true })); + }, nativeValue); + } +}; + const handlePropertyValueInput = async ( page: Page, ruleLocator: ReturnType, @@ -413,7 +462,7 @@ const handlePropertyValueInput = async ( // Fill the input only if it's visible if (await inputElement.isVisible()) { // Convert object values to JSON strings - const stringValue = isObject(value) ? JSON.stringify(value) : String(value); + const stringValue = isObject(value) ? JSON.stringify(value) : value; const apiResponsePromise = isEntityRefProperty ? page.waitForResponse('/api/v1/search/aggregate?*value=.%2A*') @@ -425,11 +474,15 @@ const handlePropertyValueInput = async ( await apiResponsePromise; } - await inputElement.fill(stringValue); + await fillPropertyValue(inputElement, stringValue); - // Press Enter for multiselect operators and date types - if ( - MULTISELECT_OPERATORS.includes(operator) || + if (MULTISELECT_OPERATORS.includes(operator)) { + await page + .locator('[role="listbox"]:visible') + .getByRole('option', { name: String(value), exact: true }) + .first() + .click(); + } else if ( ((operator === 'equal' || operator === 'not_equal') && propertyType === 'dateTime-cp') || propertyType === 'date-cp' @@ -440,7 +493,8 @@ const handlePropertyValueInput = async ( // Handle entity reference selection if (isEntityRefProperty) { await page - .locator(`.ant-select-dropdown:visible [title*="${value as string}"]`) + .locator('[role="listbox"]:visible [role="option"]') + .filter({ hasText: value as string }) .first() .click(); } @@ -459,21 +513,21 @@ export const applyCustomPropertyFilter = async ( await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), 'Custom Properties', true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), entityType, true ); await selectOption( page, - ruleLocator.locator('.rule--field .ant-select'), + ruleLocator.locator('.rule--field'), propertyName, true ); @@ -481,7 +535,7 @@ export const applyCustomPropertyFilter = async ( const operatorLabel = getOperatorLabel(operator); await selectOption( page, - ruleLocator.locator('.rule--operator .ant-select'), + ruleLocator.locator('.rule--operator'), operatorLabel ); @@ -495,9 +549,9 @@ export const applyCustomPropertyFilter = async ( const endInput = ruleLocator.locator('.rule--value input').last(); await startInput.click(); - await startInput.fill(String(rangeValue.start)); + await fillPropertyValue(startInput, rangeValue.start); await endInput.click(); - await endInput.fill(String(rangeValue.end)); + await fillPropertyValue(endInput, rangeValue.end); await page.keyboard.press('Enter'); } else { @@ -531,28 +585,37 @@ export const verifySearchResults = async ( if (shouldBeVisible) { if (!(await dashboardCard.isVisible())) { - await expect - .poll( - async () => { - const retryResponse = await page.request.get(response.url()); - - if (!retryResponse.ok()) { - return false; + // Re-query the backend through an authenticated context. `page.request` + // shares cookies but NOT the app's `Authorization: Bearer ` header, + // so re-fetching the search URL with it returns 401 "Token not present" + // and the poll would never resolve. getApiContext attaches the token. + const { apiContext, afterAction } = await getApiContext(page); + try { + await expect + .poll( + async () => { + const retryResponse = await apiContext.get(response.url()); + + if (!retryResponse.ok()) { + return false; + } + + const searchData = + (await retryResponse.json()) as SearchResponseData; + + return searchData.hits.hits.some( + (hit) => hit._source?.fullyQualifiedName === dashboardFQN + ); + }, + { + intervals: [1000, 2000, 5000], + timeout: 30000, } - - const searchData = - (await retryResponse.json()) as SearchResponseData; - - return searchData.hits.hits.some( - (hit) => hit._source?.fullyQualifiedName === dashboardFQN - ); - }, - { - intervals: [1000, 2000, 5000], - timeout: 30000, - } - ) - .toBe(true); + ) + .toBe(true); + } finally { + await afterAction(); + } await showAdvancedSearchDialog(page); const retrySearchResponse = page.waitForResponse(searchResponsePattern); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts index 37c05090bc17..f7f55be38120 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/domain.ts @@ -623,6 +623,25 @@ export const fillDomainForm = async ( .click(); }; +/** + * Submit the AddDomain/AddDataProduct drawer via its footer Save button. + * + * The drawer is a SlideoutMenu whose footer height is coupled to its + * (re-rendering) body, so the footer — and the Save button inside it — keeps + * shifting while the form settles. A pointer `click()` gates on Playwright's + * "stable" actionability check and can spin until the test times out + * ("element is not stable" → "element was detached from the DOM"), especially + * under CI load. save-btn is a native -
- )} - - + {showFilteredResourceCount && ( + + + + {t('message.click-here-to-view-assets-on-explore')} + + + + )} + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less b/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less index 89be3b58a508..df9c355a0090 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/QueryBuilderWidgetV1/query-builder-widget-v1.less @@ -11,18 +11,8 @@ * limitations under the License. */ -@import (reference) '../../../styles/variables.less'; - .query-builder-card { - background-color: @grey-6; - .ant-alert-info { - background-color: @blue-8; - border-color: @blue-7; - - .ant-alert-icon { - color: @blue-7; - } - } + background-color: var(--color-bg-secondary); } .query-builder-form-field @@ -37,7 +27,7 @@ position: absolute !important; margin-top: 0; right: 0; - top: -56px; // updating this as size of button is increased + top: -56px; display: block; } } @@ -50,12 +40,6 @@ } .query-builder-form-field { - .ant-select-disabled.ant-select:not(.ant-select-customize-input) - .ant-select-selector { - color: @black; - background-color: @background-color; - } - .hide--line.one--child { margin-top: 0; padding-top: var(--om-space-16); @@ -75,10 +59,6 @@ .group--field { width: 180px; - .ant-select { - width: 100% !important; - } - label { font-weight: var(--om-font-weight-regular); margin-bottom: var(--om-space-6); @@ -91,7 +71,7 @@ .rule.group-or-rule { .rule--header { - .ant-btn-group { + .rule--btn-group { margin: 0 !important; align-self: flex-start; } @@ -106,10 +86,6 @@ .group--field { margin: 0; flex: 0 1 25%; - - .ant-select { - min-width: 100% !important; // override the inline min-width style of the select provided by antd - } } } @@ -129,10 +105,6 @@ display: none; } - .rule-container .ant-btn-group { - visibility: visible; - } - .action.action--ADD-RULE { position: static !important; margin-top: var(--om-space-8); @@ -152,19 +124,11 @@ .widget--widget { margin: 0; flex: 1; - - .ant-col { - padding: 0 !important; // remove padding from ant-col inline styling by antd - } } .rule--operator, .rule--value .rule--widget { width: 100%; - - .ant-select { - min-width: 100% !important; // override the inline min-width style of the select provided by antd - } } } } @@ -199,10 +163,6 @@ } } } - - .rule-container .ant-btn-group { - visibility: visible; - } } } @@ -218,47 +178,42 @@ } .json-logic-field-select { - .ant-select-item-group { - padding-left: var(--om-space-8); + .item-group { + padding-left: 8px; position: relative; - color: @text-color; - font-size: var(--om-font-size-sm); - background-color: @grey-6; + color: var(--color-text-primary); + font-size: 14px; + background-color: var(--color-bg-secondary); } - /* Add vertical line for children */ - .ant-select-item-option-grouped { + .item-option-grouped { position: relative; - padding-left: var(--om-space-32); - /* Indentation for child items */ + padding-left: 32px; } - /* Add vertical line before each child */ - .ant-select-item-option-grouped::before { + .item-option-grouped::before { content: ''; position: absolute; left: 16px; top: 0; bottom: 0; width: 1px; - background-color: @border-color; + background-color: var(--color-border-primary); } - /* Adjust line height for last child */ - .ant-select-item-option-grouped:last-child::before { + .item-option-grouped:last-child::before { height: 16px; bottom: auto; } - /* Add horizontal connector for each child */ - .ant-select-item-option-grouped::after { + .item-option-grouped::after { content: ''; position: absolute; left: 16px; top: 16px; width: 10px; height: 1px; - background-color: @border-color; + background-color: var(--color-border-primary); } } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TagsSection/TagsSection.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TagsSection/TagsSection.test.tsx index 3f214cde9318..19c42b23c3b7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TagsSection/TagsSection.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TagsSection/TagsSection.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -21,9 +21,9 @@ import { } from '../../../generated/type/tagLabel'; import TagsSection from './TagsSection'; -// Mock @react-awesome-query-builder/antd -jest.mock('@react-awesome-query-builder/antd', () => ({ - ...jest.requireActual('@react-awesome-query-builder/antd'), +// Mock @react-awesome-query-builder/ui +jest.mock('@react-awesome-query-builder/ui', () => ({ + ...jest.requireActual('@react-awesome-query-builder/ui'), Config: {}, Utils: { loadFromJsonLogic: jest.fn(), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts index 487accff3718..0ba544fddfb3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -12,7 +12,6 @@ */ import { - AntdConfig, type AsyncFetchListValuesResult, type BasicConfig, type Field, @@ -20,7 +19,7 @@ import { type ListItem, type ListValues, type SelectFieldSettings, -} from '@react-awesome-query-builder/antd'; +} from '@react-awesome-query-builder/ui'; import { debounce, isEmpty, sortBy, toLower } from 'lodash'; import { SearchOutputType, @@ -51,6 +50,7 @@ import { getCustomPropertyMomentFormat } from './CustomProperty.utils'; import { buildTermQuery } from './elasticsearchQueryBuilder'; import { getEntityName } from './EntityNameUtils'; import { t } from './i18next/LocalUtil'; +import { OMConfig } from './QueryBuilderOMConfig'; import { renderQueryBuilderFilterButtons } from './QueryBuilderUtils'; import { parseBucketsData } from './SearchPureUtils'; const ENUM_ASYNC_FETCH_PAGE_SIZE = 100; @@ -58,7 +58,7 @@ const ENUM_ASYNC_FETCH_PAGE_SIZE = 100; type OMField = Field & { __omPropertyType: CustomPropertySummary['type'] }; class AdvancedSearchClassBase { - baseConfig = AntdConfig; + baseConfig = OMConfig; configTypes: BasicConfig['types'] = { ...this.baseConfig.types, multiselect: { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts index f489d9f5ce77..c060c154a691 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchPureUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { OldJsonTree } from '@react-awesome-query-builder/antd'; +import type { OldJsonTree } from '@react-awesome-query-builder/ui'; import { isArray, isEmpty, toLower } from 'lodash'; import type { Bucket } from 'Models'; import type { ExploreQuickFilterField } from '../components/Explore/ExplorePage.interface'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx index 258c07b8109d..c313c48d887a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +11,7 @@ * limitations under the License. */ -import { FieldOrGroup } from '@react-awesome-query-builder/antd'; +import { FieldOrGroup } from '@react-awesome-query-builder/ui'; import { SearchOutputType } from '../components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface'; import { AssetsOfEntity } from '../components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; import { SearchDropdownOption } from '../components/SearchDropdown/SearchDropdown.interface'; @@ -78,8 +78,8 @@ jest.mock('./AdvancedSearchClassBase', () => ({ const mockUuid = jest.fn(); let uuidCounter = 0; -jest.mock('@react-awesome-query-builder/antd', () => ({ - ...jest.requireActual('@react-awesome-query-builder/antd'), +jest.mock('@react-awesome-query-builder/ui', () => ({ + ...jest.requireActual('@react-awesome-query-builder/ui'), Utils: { uuid: () => mockUuid(), }, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx index 1f72011955b6..051935f6eb3a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchUtils.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,19 +11,16 @@ * limitations under the License. */ -import Icon, { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons'; -import { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon'; +import { Button } from '@openmetadata/ui-core-components'; import { Field, FieldOrGroup, ListValues, RenderSettings, ValueSource, -} from '@react-awesome-query-builder/antd'; -import { Button, Checkbox, MenuProps, Radio, Space, Typography } from 'antd'; +} from '@react-awesome-query-builder/ui'; +import { Plus, Trash01, X } from '@untitledui/icons'; import { isArray, isEmpty } from 'lodash'; -import React from 'react'; -import { ReactComponent as IconDeleteColored } from '../assets/svg/ic-delete-colored.svg'; import ProfilePicture from '../components/common/ProfilePicture/ProfilePicture'; import { SearchOutputType } from '../components/Explore/AdvanceSearchProvider/AdvanceSearchProvider.interface'; import { ExploreQuickFilterField } from '../components/Explore/ExplorePage.interface'; @@ -39,6 +36,8 @@ import { t } from './i18next/LocalUtil'; import jsonLogicSearchClassBase from './JSONLogicSearchClassBase'; import searchClassBase from './SearchClassBase'; +type DropdownItem = { key: string; label: JSX.Element }; + export const getDropDownItems = (index: string): ExploreQuickFilterField[] => { return searchClassBase.getDropDownItems(index); }; @@ -50,48 +49,47 @@ export const renderAdvanceSearchButtons: RenderSettings['renderButton'] = ( if (type === 'delRule') { return ( - - } + ); - } else if (type === 'addRule') { + } + + if (type === 'addRule') { return ( ); - } else if (type === 'addGroup') { + } + + if (type === 'addGroup') { return ( ); - } else if (type === 'delGroup') { + } + + if (type === 'delGroup') { return ( - void} /> ); @@ -108,19 +106,18 @@ export const generateSearchDropdownLabel = ( hideCounts = false, singleSelect = false ) => { - const InputComponent = singleSelect ? Radio : Checkbox; - return (
- - + {showProfilePicture && ( )}
- - + {option.description && ( - + {option.description} - + )}
-
+
{!hideCounts && getCountBadge(option.count, 'm-r-sm', false)} ); @@ -167,7 +162,7 @@ export const getSearchDropdownLabels = ( showProfilePicture = false, hideCounts = false, singleSelect = false -): MenuProps['items'] => { +): DropdownItem[] => { if (isArray(optionsArray)) { const sortedOptions = optionsArray.sort( (a, b) => (b.count ?? 0) - (a.count ?? 0) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsPureUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsPureUtils.ts index 86bd0e299f0c..44812304cb60 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsPureUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsPureUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +11,7 @@ * limitations under the License. */ -import { Config, Utils as QbUtils } from '@react-awesome-query-builder/antd'; +import { Config, Utils as QbUtils } from '@react-awesome-query-builder/ui'; import { isEmpty } from 'lodash'; import { Bucket } from 'Models'; import Qs from 'qs'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsUtils.test.tsx index 88026bb55cec..dfa0a8a17edd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CuratedAssetsUtils.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -40,7 +40,7 @@ jest.mock('./RouterUtils', () => ({ getExplorePath: jest.fn().mockReturnValue('/explore'), })); -jest.mock('@react-awesome-query-builder/antd', () => ({ +jest.mock('@react-awesome-query-builder/ui', () => ({ Utils: { checkTree: jest.fn().mockReturnValue({}), loadTree: jest.fn().mockReturnValue({}), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts index 9a0cd92f1581..0ecb7442f89b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DataContract/DataContractUtils.ts @@ -284,7 +284,6 @@ export const getSematicRuleFields = () => { fieldSettings: { asyncFetch: jsonLogicSearchClassBase.autoCompleteTier, useAsyncSearch: true, - listValues: jsonLogicSearchClassBase.autoCompleteTier, }, }, }, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts index 7f653743a944..2fe512be7d5d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/JSONLogicSearchClassBase.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -11,7 +11,6 @@ * limitations under the License. */ import { - AntdConfig, AsyncFetchListValuesResult, Config, FieldOrGroup, @@ -19,7 +18,7 @@ import { ListItem, Operators, SelectFieldSettings, -} from '@react-awesome-query-builder/antd'; +} from '@react-awesome-query-builder/ui'; import { get, sortBy, toLower } from 'lodash'; import { LIST_VALUE_OPERATORS, @@ -45,11 +44,12 @@ import { searchQuery } from '../rest/searchAPI'; import { getTags } from '../rest/tagAPI'; import advancedSearchClassBase from './AdvancedSearchClassBase'; import { t } from './i18next/LocalUtil'; +import { OMConfig } from './QueryBuilderOMConfig'; import { getFieldsByKeys } from './QueryBuilderPureUtils'; import { renderJSONLogicQueryBuilderButtons } from './QueryBuilderUtils'; class JSONLogicSearchClassBase { - baseConfig = AntdConfig as Config; + baseConfig = OMConfig as Config; configTypes: Config['types'] = { ...this.baseConfig.types, multiselect: { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderOMConfig.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderOMConfig.tsx new file mode 100644 index 000000000000..38ec3b01bf0f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderOMConfig.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2026 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { BasicConfig } from '@react-awesome-query-builder/ui'; +import { BasicConfig as QbBasicConfig } from '@react-awesome-query-builder/ui'; +import OMBooleanWidget from './queryBuilderWidgets/OMBooleanWidget'; +import OMConjs from './queryBuilderWidgets/OMConjs'; +import OMDateWidget from './queryBuilderWidgets/OMDateWidget'; +import OMFieldSelect from './queryBuilderWidgets/OMFieldSelect'; +import OMMultiSelectWidget from './queryBuilderWidgets/OMMultiSelectWidget'; +import OMNumberWidget from './queryBuilderWidgets/OMNumberWidget'; +import OMSelectWidget from './queryBuilderWidgets/OMSelectWidget'; +import OMTextWidget from './queryBuilderWidgets/OMTextWidget'; + +export const OMConfig: BasicConfig = { + ...QbBasicConfig, + settings: { + ...QbBasicConfig.settings, + renderField: (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) => , + renderConjs: (props) => , + }, + widgets: { + ...QbBasicConfig.widgets, + text: { + ...QbBasicConfig.widgets.text, + factory: (props) => , + }, + textarea: { + ...QbBasicConfig.widgets.textarea, + factory: (props) => , + }, + number: { + ...QbBasicConfig.widgets.number, + factory: (props) => , + }, + select: { + ...QbBasicConfig.widgets.select, + factory: (props) => , + }, + multiselect: { + ...QbBasicConfig.widgets.multiselect, + factory: (props) => , + }, + boolean: { + ...QbBasicConfig.widgets.boolean, + factory: (props) => , + }, + date: { + ...QbBasicConfig.widgets.date, + factory: (props) => , + }, + time: { + ...QbBasicConfig.widgets.time, + factory: (props) => , + }, + datetime: { + ...QbBasicConfig.widgets.datetime, + factory: (props) => , + }, + }, +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderPureUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderPureUtils.ts index a91ab845ae77..7d4e2c0fccb0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderPureUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderPureUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,7 +15,7 @@ import type { Fields, OldJsonItem, OldJsonTree, -} from '@react-awesome-query-builder/antd'; +} from '@react-awesome-query-builder/ui'; import { isBoolean, isEmpty, isUndefined } from 'lodash'; import { EntityReferenceFields } from '../enums/AdvancedSearch.enum'; import { EntityType } from '../enums/entity.enum'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts index e00ef8b43d10..3d00dc6e2e2e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Fields } from '@react-awesome-query-builder/antd'; +import { Fields } from '@react-awesome-query-builder/ui'; import { EntityType } from '../enums/entity.enum'; import { QueryFieldInterface, diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx index c2b1bee191b0..cd0bb74b92c4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QueryBuilderUtils.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2024 Collate. + * Copyright 2026 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -10,9 +10,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; -import type { RenderSettings } from '@react-awesome-query-builder/antd'; -import { Button } from 'antd'; +import { Button } from '@openmetadata/ui-core-components'; +import type { RenderSettings } from '@react-awesome-query-builder/ui'; +import { Plus, X } from '@untitledui/icons'; import { t } from './i18next/LocalUtil'; export const renderQueryBuilderFilterButtons: RenderSettings['renderButton'] = ( @@ -22,32 +22,38 @@ export const renderQueryBuilderFilterButtons: RenderSettings['renderButton'] = ( if (type === 'delRule') { return ( - ); } @@ -61,30 +67,36 @@ export const renderJSONLogicQueryBuilderButtons: RenderSettings['renderButton'] if (type === 'delRule') { return ( -