From cde5e2ff60dfe4254d0aaec27bf04e72985b1989 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 13 Oct 2026 16:57:10 +0530 Subject: [PATCH 1/6] feat(ui): move TeamTypeSelect and ColorPicker to core components TeamTypeSelect uses the core Select and core icons. ColorPicker keeps its free-hex swatch + text pair on core Input and reports a plain string; the core COLOR_PICKER field is a fixed palette, so it cannot replace it for theme colours. --- .../eslint-rules/openmetadata-antd.test.mjs | 2 +- .../resources/ui/eslint-suppressions.json | 10 ---- .../ColorPicker/ColorPicker.component.tsx | 57 ++++++++++++------- .../common/ColorPicker/ColorPicker.test.tsx | 13 ++++- .../ColorPicker/color-picker.style.less | 18 ------ .../TeamTypeSelect.component.tsx | 41 ++++++------- .../TeamTypeSelect/TeamTypeSelect.test.tsx | 38 ++++++++++--- .../AppearanceConfigSettingsPage.tsx | 4 +- .../main/resources/ui/src/utils/formUtils.tsx | 4 -- 9 files changed, 104 insertions(+), 83 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/color-picker.style.less diff --git a/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs b/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs index 4aaec83d0924..082339ae2ba2 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs +++ b/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs @@ -163,7 +163,7 @@ test('the app config never suppresses a migrated component', async () => { // Exact, so re-running --suppress-rule cannot quietly absorb new antd usage. // Lower this when a PR removes antd imports and prunes the baseline. - const EXPECTED_SUPPRESSED_ANTD_SYMBOLS = 357; + const EXPECTED_SUPPRESSED_ANTD_SYMBOLS = 352; const suppressedSymbols = Object.values(suppressions).reduce( (total, rules) => total + (rules['openmetadata-antd/no-antd-import']?.count ?? 0), diff --git a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json index 81e5b566de3c..879d20a6660a 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json +++ b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json @@ -1343,11 +1343,6 @@ "count": 1 } }, - "src/components/common/ColorPicker/ColorPicker.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 2 - } - }, "src/components/common/DatePicker/DatePicker.tsx": { "openmetadata-antd/no-antd-import": { "count": 2 @@ -1483,11 +1478,6 @@ "count": 1 } }, - "src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/common/UserTeamSelectableListSearchInput/UserTeamSelectableListSearchInput.component.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx index 9a089da92a0f..f363fb1b6d6c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx @@ -10,30 +10,49 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Input, InputProps } from 'antd'; +import { Box, Input } from '@openmetadata/ui-core-components'; import { useTranslation } from 'react-i18next'; -import './color-picker.style.less'; -const ColorPicker = (props: InputProps) => { +export interface ColorPickerProps { + id?: string; + value?: string; + onBlur?: () => void; + onChange?: (value: string) => void; +} + +const ColorPicker = ({ + id, + value = '', + onBlur, + onChange, +}: ColorPickerProps) => { const { t } = useTranslation(); - const { id, ...rest } = props; + const testId = id ? `${id}-color` : 'color'; return ( - - - - + + + + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.test.tsx index b34b5fc6ab61..8edb9ab50d0a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.test.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import ColorPicker from './ColorPicker.component'; describe('ColorPicker component', () => { @@ -27,4 +27,15 @@ describe('ColorPicker component', () => { expect(await screen.findByTestId('color-picker')).toHaveValue('#000000'); expect(await screen.findByTestId('color-input')).toHaveValue('#000000'); }); + + it('reports a typed hex code as a plain string', () => { + const onChange = jest.fn(); + render(); + + fireEvent.change(screen.getByTestId('primaryColor-color-input'), { + target: { value: '#123456' }, + }); + + expect(onChange).toHaveBeenCalledWith('#123456'); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/color-picker.style.less b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/color-picker.style.less deleted file mode 100644 index 49b340eb0766..000000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/color-picker.style.less +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 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. - */ -.ant-input-group .ant-input.style-color-picker { - width: 15%; -} -.ant-input-group .ant-input.style-color-input { - width: 85%; -} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx index 971716fe203b..c3d95a51a34b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx @@ -11,10 +11,10 @@ * limitations under the License. */ -import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; -import { Box, Button } from '@openmetadata/ui-core-components'; -import { Select } from 'antd'; +import { Box, Button, Select } from '@openmetadata/ui-core-components'; +import { Check, XClose } from '@openmetadata/ui-core-components/icons'; import { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { TeamType } from '../../../generated/entity/teams/team'; import { getTeamOptionsFromType } from '../../../utils/TeamUtils'; import { TeamTypeSelectProps } from './TeamTypeSelect.interface'; @@ -26,12 +26,9 @@ function TeamTypeSelect({ updateTeamType, parentTeamType, }: TeamTypeSelectProps) { + const { t } = useTranslation(); const [value, setValue] = useState(teamType); - const handleSelect = (type: TeamType) => { - setValue(type); - }; - const handleCancel = () => { handleShowTypeSelector(false); }; @@ -42,13 +39,13 @@ function TeamTypeSelect({ const options = useMemo(() => { const options = getTeamOptionsFromType(parentTeamType).map((type) => ({ + id: type, label: type, - value: type, })); return showGroupOption ? options - : options.filter((opt) => opt.value !== TeamType.Group); + : options.filter((opt) => opt.id !== TeamType.Group); }, [parentTeamType, showGroupOption]); // Editing the team type must not toggle its parent collapsible panel. @@ -62,11 +59,13 @@ function TeamTypeSelect({ itemClassName="layout-space-item" onClick={(e) => e.stopPropagation()}> + onClick={handleCancel} + /> + onClick={handleSubmit} + /> ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.test.tsx index 9e36eeaba3a2..d0fbbb0d7749 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.test.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { fireEvent, render } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import { TeamType } from '../../../generated/entity/teams/team'; import TeamTypeSelect from './TeamTypeSelect.component'; @@ -26,12 +26,9 @@ const mockProps = { }; jest.mock('../../../utils/TeamUtils', () => ({ - getTeamOptionsFromType: jest.fn().mockReturnValue([ - { label: 'BusinessUnit', value: 'BusinessUnit' }, - { label: 'Division', value: 'Division' }, - { label: 'Department', value: 'Department' }, - { label: 'Group', value: 'Group' }, - ]), + getTeamOptionsFromType: jest + .fn() + .mockReturnValue(['BusinessUnit', 'Division', 'Department', 'Group']), })); describe('TeamTypeSelect', () => { @@ -55,4 +52,31 @@ describe('TeamTypeSelect', () => { expect(updateTeamType).toHaveBeenCalled(); }); + + it('should save the team type picked from the list', async () => { + render(); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /team-type/ })); + }); + await act(async () => { + fireEvent.click(screen.getByRole('option', { name: 'Division' })); + }); + fireEvent.click(screen.getByTestId('save-btn')); + + expect(updateTeamType).toHaveBeenCalledWith(TeamType.Division); + }); + + it('should not offer Group when the group option is hidden', async () => { + render(); + + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: /team-type/ })); + }); + + expect(screen.queryByRole('option', { name: 'Group' })).toBeNull(); + expect( + screen.getByRole('option', { name: 'Division' }) + ).toBeInTheDocument(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx index 9a047b150132..05ce5ca01816 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx @@ -353,9 +353,7 @@ const AppearanceConfigSettingsPage = () => { id={field.name} value={control.value ?? ''} onBlur={control.onBlur} - onChange={(e) => - control.onChange(e.target.value) - } + onChange={control.onChange} /> {fieldState.error && ( diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx index 3d6295fcabf2..762701adbbd7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/formUtils.tsx @@ -36,7 +36,6 @@ import { ComponentProps, Fragment, ReactNode } from 'react'; import type { Placement } from 'react-aria'; import AsyncSelectList from '../components/common/AsyncSelectList/AsyncSelectList'; import { AsyncSelectListProps } from '../components/common/AsyncSelectList/AsyncSelectList.interface'; -import ColorPicker from '../components/common/ColorPicker/ColorPicker.component'; import DomainSelectableList from '../components/common/DomainSelectableList/DomainSelectableList.component'; import { DomainSelectableListProps } from '../components/common/DomainSelectableList/DomainSelectableList.interface'; import FilterPattern from '../components/common/FilterPattern/FilterPattern'; @@ -193,9 +192,6 @@ const FIELD_ELEMENT_RENDERERS: Partial< ), }; }, - [FieldTypes.COLOR_PICKER]: ({ props }) => ({ - element: , - }), [FieldTypes.COMPONENT]: ({ props }) => ({ element: props.children, }), From cd048d5567cefda92c1c3daa37fe02c2a54e3265 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 13 Oct 2026 17:05:01 +0530 Subject: [PATCH 2/6] feat(ui): move the query table picker and classic term boosts to core QueryUsedByOtherTable picks tables with the core DataAssetAsyncSelectList and reports entity references, so QueryCard no longer maps antd options. The classic search settings reuse the AI-mode TermBoostCard instead of the antd TermBoost copy. --- .../eslint-rules/openmetadata-antd.test.mjs | 2 +- .../resources/ui/eslint-suppressions.json | 20 -- .../e2e/Features/QueryEntity.spec.ts | 13 +- .../Database/TableQueries/QueryCard.tsx | 20 +- .../QueryUsedByOtherTable.component.tsx | 123 +++-------- .../QueryUsedByOtherTable.test.tsx | 69 +++--- .../TableQueries/TableQueries.interface.ts | 3 +- .../EntitySearchSettings.tsx | 1 - .../entity-search-settings.less | 4 - .../TermBoost/TermBoost.test.tsx | 103 --------- .../SearchSettings/TermBoost/TermBoost.tsx | 197 ------------------ .../SearchSettings/TermBoost/term-boost.less | 78 ------- .../TermBoostList/TermBoostList.test.tsx | 65 +++--- .../TermBoostList/TermBoostList.tsx | 20 +- .../SearchSettingsPage/SearchSettingsPage.tsx | 1 - .../SearchSettingsPage/search-settings.less | 3 - 16 files changed, 120 insertions(+), 602 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.test.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/term-boost.less diff --git a/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs b/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs index 082339ae2ba2..3e2b3b9d6881 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs +++ b/openmetadata-ui/src/main/resources/ui/eslint-rules/openmetadata-antd.test.mjs @@ -163,7 +163,7 @@ test('the app config never suppresses a migrated component', async () => { // Exact, so re-running --suppress-rule cannot quietly absorb new antd usage. // Lower this when a PR removes antd imports and prunes the baseline. - const EXPECTED_SUPPRESSED_ANTD_SYMBOLS = 352; + const EXPECTED_SUPPRESSED_ANTD_SYMBOLS = 346; const suppressedSymbols = Object.values(suppressions).reduce( (total, rules) => total + (rules['openmetadata-antd/no-antd-import']?.count ?? 0), diff --git a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json index 879d20a6660a..f7aeba39f4ae 100644 --- a/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json +++ b/openmetadata-ui/src/main/resources/ui/eslint-suppressions.json @@ -1008,21 +1008,6 @@ "count": 1 } }, - "src/components/Database/TableQueries/QueryCard.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, - "src/components/Database/TableQueries/TableQueries.interface.ts": { - "openmetadata-antd/no-antd-import": { - "count": 1 - } - }, "src/components/Database/TableQueries/TableQueries.tsx": { "openmetadata-antd/no-antd-import": { "count": 3 @@ -1188,11 +1173,6 @@ "count": 1 } }, - "src/components/SearchSettings/TermBoost/TermBoost.tsx": { - "openmetadata-antd/no-antd-import": { - "count": 3 - } - }, "src/components/ServiceInsights/AgentsStatusWidget/AgentsStatusWidget.tsx": { "openmetadata-antd/no-antd-import": { "count": 1 diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts index 15cab5e4b0c9..6f00c9e331f9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts @@ -201,14 +201,19 @@ test('Query Entity', async ({ page }) => { await page.click(CODE_EDITOR_LINE, { clickCount: 3 }); await page.keyboard.press('Backspace'); await page.keyboard.type(`${queryData.queryUsedIn.table1}`); - await page.click('[data-testid="edit-query-used-in"]'); const tableSearchResponse = page.waitForResponse( - '/api/v1/search/query?q=*&index=table*' + (response) => + response.url().includes('/api/v1/search/query') && + response.url().includes('index=table') && + response.url().includes(queryData.queryUsedIn.table2) ); - await page.keyboard.type(queryData.queryUsedIn.table2); + await page + .getByTestId('edit-query-used-in') + .getByRole('combobox') + .fill(queryData.queryUsedIn.table2); await tableSearchResponse; await page - .locator('.ant-select-dropdown:visible .ant-select-item-option') + .getByRole('option') .filter({ has: page.getByText(queryData.queryUsedIn.table2, { exact: true }), }) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryCard.tsx index 30f5e471682a..f9902026ea8f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryCard.tsx @@ -21,7 +21,6 @@ import { Typography, } from '@openmetadata/ui-core-components'; import { Copy01 } from '@openmetadata/ui-core-components/icons'; -import { DefaultOptionType } from 'antd/lib/select'; import classNames from 'classnames'; import { isUndefined, split } from 'lodash'; import { Duration } from 'luxon'; @@ -41,6 +40,7 @@ import { } from '../../../constants/Query.constant'; import { CSMode } from '../../../enums/codemirror.enum'; import { EntityTabs, EntityType } from '../../../enums/entity.enum'; +import { EntityReference } from '../../../generated/type/entityReference'; import { useClipboard } from '../../../hooks/useClipBoard'; import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation'; import { useFqn } from '../../../hooks/useFqn'; @@ -86,7 +86,7 @@ const QueryCard: FC = ({ query: query.query, isLoading: false, }); - const [selectedTables, setSelectedTables] = useState(); + const [selectedTables, setSelectedTables] = useState(); const { isAllowExpand, queryDate } = useMemo(() => { const queryArr = split(query.query, '\n'); @@ -130,21 +130,7 @@ const QueryCard: FC = ({ const updatedData = { ...query, query: query.query !== sqlQuery.query ? sqlQuery.query : query.query, - queryUsedIn: isUndefined(selectedTables) - ? query.queryUsedIn - : selectedTables.map((option) => { - const existingTable = query.queryUsedIn?.find( - (table) => table.id === option.value - ); - - return ( - existingTable ?? { - id: (option.value as string) ?? '', - displayName: option.labelName as string, - type: EntityType.TABLE, - } - ); - }), + queryUsedIn: selectedTables ?? query.queryUsedIn, }; if (query.query !== sqlQuery.query || !isUndefined(selectedTables)) { await onQueryUpdate(updatedData, 'query'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx index 455ef5e6b9af..39df36b9b43d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx @@ -17,25 +17,17 @@ import { PopoverTrigger, Typography, } from '@openmetadata/ui-core-components'; - -import { DefaultOptionType } from 'antd/lib/select'; -import { isArray, isUndefined, slice, uniqBy } from 'lodash'; -import { useEffect, useMemo, useState } from 'react'; +import { isUndefined, slice } from 'lodash'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { - INITIAL_PAGING_VALUE, - PAGE_SIZE_MEDIUM, -} from '../../../../constants/constants'; import { QUERY_USED_BY_TABLE_VIEW_CAP } from '../../../../constants/Query.constant'; import { EntityType } from '../../../../enums/entity.enum'; import { SearchIndex } from '../../../../enums/search.enum'; -import { searchQuery } from '../../../../rest/searchAPI'; import { getEntityName } from '../../../../utils/EntityNameUtils'; import { getEntityDetailsPath } from '../../../../utils/RouterUtils'; -import { AsyncSelect } from '../../../common/AsyncSelect/AsyncSelect'; -import Loader from '../../../common/Loader/Loader'; -import getEntityLabel from '../../../Entity/EntityLabel/EntityLabel.component'; +import DataAssetAsyncSelectList from '../../../DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList'; +import { DataAssetOption } from '../../../DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface'; import { QueryUsedByOtherTableProps, QueryUsedByTable, @@ -47,8 +39,6 @@ const QueryUsedByOtherTable = ({ onChange, }: QueryUsedByOtherTableProps) => { const { t } = useTranslation(); - const [initialOptions, setInitialOptions] = useState([]); - const [isLoading, setIsLoading] = useState(false); const { topThreeTable, remainingTable } = useMemo(() => { const { queryUsedIn } = query; @@ -131,85 +121,38 @@ const QueryUsedByOtherTable = ({ [topThreeTable, remainingTable] ); - const handleOnChange = ( - _: string[], - options?: DefaultOptionType | DefaultOptionType[] - ) => { - if (isArray(options)) { - onChange(options); - } - }; - - const fetchTableEntity = async ( - searchValue = '' - ): Promise => { - try { - const response = await searchQuery({ - query: searchValue, - pageNumber: INITIAL_PAGING_VALUE, - pageSize: PAGE_SIZE_MEDIUM, - searchIndex: SearchIndex.TABLE, - }); - - return response.hits.hits.map((value) => ({ - label: getEntityLabel(value._source), - value: value._source.id, - labelName: getEntityName(value._source), - })); - } catch (error) { - return []; - } - }; - - const fetchInitialOptions = async () => { - setIsLoading(true); - try { - const options = await fetchTableEntity(); - const { queryUsedIn = [] } = query; - const selectedValue = queryUsedIn.map((table) => ({ - label: getEntityLabel(table), - value: table.id, - labelName: getEntityName(table), - })); - - setInitialOptions(uniqBy([...selectedValue, ...options], 'labelName')); - } catch (error) { - setInitialOptions([]); - } finally { - setIsLoading(false); - } - }; - const selectList = useMemo(() => { - const { queryUsedIn = [] } = query; - - const defaultValue = queryUsedIn.map((table) => table.id); - - return isLoading ? ( - - ) : ( - + const initialOptions: DataAssetOption[] = (query.queryUsedIn ?? []).map( + (table) => ({ + value: table.fullyQualifiedName, + displayName: getEntityName(table), + reference: table, + }) ); - }, [query, initialOptions, isLoading, fetchTableEntity, handleOnChange]); - useEffect(() => { - if (isEditMode) { - fetchInitialOptions(); - } - }, [isEditMode]); + return ( +
+ option.value ?? '')} + onChange={(options) => + onChange( + (Array.isArray(options) ? options : []).map( + (option) => option.reference + ) + ) + } + /> +
+ ); + }, [query.queryUsedIn, onChange, t]); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx index 14e9e991b4e4..0195a2a55613 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx @@ -13,20 +13,21 @@ import { render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; +import { SearchIndex } from '../../../../enums/search.enum'; import { Query } from '../../../../generated/entity/data/query'; import { MOCK_QUERIES } from '../../../../mocks/Queries.mock'; -import { searchQuery } from '../../../../rest/searchAPI'; -import { MOCK_EXPLORE_SEARCH_RESULTS } from '../../../Explore/Explore.mock'; +import { DataAssetAsyncSelectListProps } from '../../../DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList.interface'; import { QueryUsedByOtherTableProps } from '../TableQueries.interface'; import QueryUsedByOtherTable from './QueryUsedByOtherTable.component'; -jest.mock('../../../common/AsyncSelect/AsyncSelect', () => ({ - AsyncSelect: jest - .fn() - .mockImplementation(() =>
AsyncSelect.component
), -})); +const mockDataAssetSelect = jest + .fn() + .mockImplementation(() =>
DataAssetAsyncSelectList
); -jest.mock('../../../../rest/searchAPI'); +jest.mock( + '../../../DataAssets/DataAssetAsyncSelectList/DataAssetAsyncSelectList', + () => (props: DataAssetAsyncSelectListProps) => mockDataAssetSelect(props) +); const mockProps: QueryUsedByOtherTableProps = { query: MOCK_QUERIES[0] as Query, @@ -35,10 +36,6 @@ const mockProps: QueryUsedByOtherTableProps = { }; describe('QueryUsedByOtherTable test', () => { - beforeEach(() => { - (searchQuery as jest.Mock).mockResolvedValue(MOCK_EXPLORE_SEARCH_RESULTS); - }); - it('Component should render', async () => { render(, { wrapper: MemoryRouter, @@ -71,40 +68,36 @@ describe('QueryUsedByOtherTable test', () => { ); }); - it('Should display select box if edit mode is true', async () => { + it('Should search tables and preselect the current ones in edit mode', async () => { render(, { wrapper: MemoryRouter, }); - const selectField = await screen.findByText('AsyncSelect.component'); - expect(selectField).toBeInTheDocument(); - }); + expect( + await screen.findByText('DataAssetAsyncSelectList') + ).toBeInTheDocument(); - it('Should fetch initial dropdown list in edit mode', async () => { - const mockSearchQuery = searchQuery as jest.Mock; - render(, { - wrapper: MemoryRouter, - }); - const selectField = await screen.findByText('AsyncSelect.component'); - - expect(selectField).toBeInTheDocument(); - expect(mockSearchQuery).toHaveBeenCalledWith({ - query: '', - pageNumber: 1, - pageSize: 25, - searchIndex: 'table', - }); - }); + const props = mockDataAssetSelect.mock.calls[0][0]; + const usedIn = MOCK_QUERIES[0].queryUsedIn ?? []; - it('Loader should be visible while loading the initial options', async () => { - render(, { - wrapper: MemoryRouter, - }); + expect(props.searchIndex).toBe(SearchIndex.TABLE); + expect(props.multiple).toBe(true); + expect(props.value).toEqual(usedIn.map((t) => t.fullyQualifiedName)); + }); - expect(screen.getByTestId('loader')).toBeInTheDocument(); + it('Should report the picked tables as entity references', async () => { + const onChange = jest.fn(); + render( + , + { wrapper: MemoryRouter } + ); + await screen.findByText('DataAssetAsyncSelectList'); - const selectField = await screen.findByText('AsyncSelect.component'); + const reference = { id: 'table-id', type: 'table', name: 'orders' }; + mockDataAssetSelect.mock.calls[0][0].onChange([ + { displayName: 'orders', value: 'db.orders', reference }, + ]); - expect(selectField).toBeInTheDocument(); + expect(onChange).toHaveBeenCalledWith([reference]); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/TableQueries.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/TableQueries.interface.ts index f417f6086467..9aed7922c293 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/TableQueries.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/TableQueries.interface.ts @@ -11,7 +11,6 @@ * limitations under the License. */ -import type { DefaultOptionType } from 'antd/lib/select'; import { HTMLAttributes } from 'react'; import { OperationPermission } from '../../../context/PermissionProvider/PermissionProvider.interface'; import { SORT_ORDER } from '../../../enums/common.enum'; @@ -60,7 +59,7 @@ export type QueryUsedByTable = { export interface QueryUsedByOtherTableProps { query: Query; isEditMode: boolean; - onChange: (value: DefaultOptionType[]) => void; + onChange: (tables: EntityReference[]) => void; } export type QuerySearchParams = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/EntitySearchSettings.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/EntitySearchSettings.tsx index 18cdcc17fb33..0289c9967bc1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/EntitySearchSettings.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/EntitySearchSettings.tsx @@ -1044,7 +1044,6 @@ const EntitySearchSettings = () => { handleDeleteTermBoost={handleDeleteTermBoost} handleTermBoostChange={handleTermBoostChange} showNewTermBoost={showNewTermBoost} - termBoostCardClassName="term-boost-card" termBoosts={searchSettings.termBoosts ?? []} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/entity-search-settings.less b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/entity-search-settings.less index d0ff0c1f29e0..385b13e3b476 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/entity-search-settings.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/EntitySeachSettings/entity-search-settings.less @@ -98,10 +98,6 @@ } } -.term-boost-card { - min-width: 100%; -} - .ranking-configuration-container { .ranking-configuration { display: flex; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.test.tsx deleted file mode 100644 index 6e69cb31d573..000000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.test.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2025 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 { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { TermBoost } from '../../../generated/configuration/searchSettings'; -import tagClassBase from '../../../utils/TagClassBase'; -import TermBoostComponent from './TermBoost'; - -jest.mock('react-i18next', () => ({ - useTranslation: () => ({ - t: (str: string) => str, - }), -})); - -jest.mock('../../../utils/TagClassBase', () => ({ - getTags: jest.fn(), -})); - -jest.mock('../../common/AsyncSelect/AsyncSelect', () => ({ - AsyncSelect: jest.fn().mockImplementation(({ api }) => ( -
-

AsyncSelect

- api('test')} - /> -
- )), -})); - -const mockTermBoost: TermBoost = { - field: 'tags.tagFQN', - value: 'PII.Sensitive', - boost: 5, -}; - -const mockProps = { - termBoost: mockTermBoost, - onTermBoostChange: jest.fn(), - onDeleteBoost: jest.fn(), -}; - -const mockTagResponse = { - data: [ - { - data: { - name: 'Sensitive', - displayName: 'Sensitive', - fullyQualifiedName: 'PII.Sensitive', - }, - }, - ], - paging: { - total: 1, - }, -}; - -describe('TermBoost Component', () => { - beforeEach(() => { - jest.clearAllMocks(); - (tagClassBase.getTags as jest.Mock).mockResolvedValue(mockTagResponse); - }); - - it('Should render component with initial values', () => { - render(); - - expect(screen.getByTestId('term-boost-impact-label')).toBeInTheDocument(); - expect(screen.getByTestId('term-boost-value')).toBeInTheDocument(); - expect(screen.getByTestId('term-boost-slider')).toBeInTheDocument(); - expect(screen.getByTestId('term-boost-select')).toBeInTheDocument(); - }); - - it('Should handle tag selection', async () => { - render(); - - const select = screen.getByTestId('term-boost-select'); - fireEvent.click(select); - - await waitFor(() => { - expect(tagClassBase.getTags).toHaveBeenCalledWith('test', 1, true); - }); - }); - - it('Should handle delete tag boost', () => { - render(); - - const deleteIcon = screen.getByTestId('delete-term-boost'); - fireEvent.click(deleteIcon); - - expect(mockProps.onDeleteBoost).toHaveBeenCalledWith('PII.Sensitive'); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.tsx deleted file mode 100644 index a57d4e25872e..000000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/TermBoost.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2025 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 Icon, { DownOutlined } from '@ant-design/icons'; -import { - Box, - Button, - Divider, - Typography, -} from '@openmetadata/ui-core-components'; -import { Slider } from 'antd'; -import { AxiosError } from 'axios'; -import classNames from 'classnames'; -import { ComponentProps, useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as Delete } from '../../../assets/svg/delete-colored.svg'; -import { TermBoost } from '../../../generated/configuration/searchSettings'; -import { getFilterOptions } from '../../../utils/SearchSettingsUtils'; -import tagClassBase from '../../../utils/TagClassBase'; -import { showErrorToast } from '../../../utils/ToastUtils'; -import { AsyncSelect } from '../../common/AsyncSelect/AsyncSelect'; -import './term-boost.less'; - -interface TermBoostProps { - className?: string; - termBoost: TermBoost; - onTermBoostChange: (termBoost: TermBoost) => void; - onDeleteBoost: (termValue: string) => void; - showNewTermBoost?: boolean; -} - -const TermBoostComponent: React.FC = ({ - className, - termBoost, - onTermBoostChange, - onDeleteBoost, - showNewTermBoost, -}) => { - const { t } = useTranslation(); - const [termBoostData, setTermBoostData] = useState({ - field: '', - value: '', - boost: 0, - }); - - const isNewBoost = showNewTermBoost || (!termBoost.field && !termBoost.value); - - useEffect(() => { - if (!isNewBoost) { - setTermBoostData({ - field: termBoost.field, - value: termBoost.value, - boost: termBoost.boost, - }); - } - }, [termBoost, isNewBoost]); - - const fetchTags = async (searchText: string, page = 1) => { - try { - const response = await tagClassBase.getTags(searchText, page, true); - - const formattedOptions = response.data.map((item) => { - const fqn = item.data.fullyQualifiedName; - let field = 'tags.tagFQN'; // default field - - // Determine field based on FQN pattern - if (fqn?.startsWith('Certification.')) { - field = 'certification.tagLabel.tagFQN'; - } else if (fqn?.startsWith('Tier.')) { - field = 'tier.tagFQN'; - } - - return { - label: ( -
- - {item.data.displayName ?? item.data.name} - - - {fqn} - -
- ), - value: fqn, - field: field, - }; - }); - - // Return PagingResponse structure for infinite scroll support - return { - data: formattedOptions, - paging: response.paging, - }; - } catch (error) { - showErrorToast(error as AxiosError); - - // Return empty PagingResponse structure on error - return { - data: [], - paging: { total: 0 }, - }; - } - }; - - const handleTagChange: NonNullable< - ComponentProps['onChange'] - > = (value, option) => { - const selectedOption = Array.isArray(option) ? option[0] : option; - const updatedData = { - ...termBoostData, - field: selectedOption?.field, - value: value as string, - }; - - setTermBoostData(updatedData); - onTermBoostChange(updatedData); - }; - - const handleBoostChange = (value: number) => { - const updatedData = { ...termBoostData, boost: value }; - - setTermBoostData(updatedData); - onTermBoostChange(updatedData); - }; - - return ( -
- - - } - value={termBoostData.value || undefined} - onChange={handleTagChange} - /> - - -
- - {t('label.boost')} - - - {termBoostData.boost} - -
-
- -
- -
- -
- ); -}; - -export default TermBoostComponent; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/term-boost.less b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/term-boost.less deleted file mode 100644 index 8f6c1ebeeaba..000000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoost/term-boost.less +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2025 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 '../../../styles/variables.less'; - -.term-boost { - background-color: var(--tw-color-bg-primary); - border: 1px solid @grey-100; - border-radius: var(--om-radius-xl); -} - -.delete-term-boost { - color: @red-12; - background-color: @red-9; - border: 1px solid @red-13; - border-radius: var(--om-radius-lg); - &:hover { - color: @red-12; - background-color: @red-9; - border: 1px solid @red-13; - } -} - -.boost-value { - color: @blue-3; -} - -.config-section-content { - border: 1px solid @border-color-4; -} - -.custom-select { - .ant-select-selector { - padding-left: 0; - padding-right: 0; - border: none !important; - } - - .ant-select-focused .ant-select-selector, - .ant-select-selector:focus, - .ant-select-selector:active, - .ant-select-open .ant-select-selector { - border: none; - box-shadow: none; - } - - &.ant-select-focused:not(.ant-select-disabled):not( - .ant-select-customize-input - ):not(.ant-select-borderless) - .ant-select-selector { - border: none; - box-shadow: none; - } - - &.ant-select-disabled { - .ant-select-selector { - background-color: transparent; - color: @black; - } - } -} - -.config-section-content - .ant-select-disabled.ant-select:not(.ant-select-customize-input) - .ant-select-selector { - background-color: transparent; - color: @black; - cursor: default; -} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.test.tsx index e07fb454378c..cff0e42b55d0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.test.tsx @@ -30,37 +30,42 @@ const mockTermBoosts: TermBoost[] = [ const mockHandleDeleteTermBoost = jest.fn(); const mockHandleTermBoostChange = jest.fn(); -jest.mock('../TermBoost/TermBoost', () => { - return jest - .fn() - .mockImplementation( - ({ termBoost, showNewTermBoost, onDeleteBoost, onTermBoostChange }) => ( -
- {termBoost.value || 'New Term Boost'} - - -
- ) - ); -}); + {termBoost?.value || 'New Term Boost'} + + + + ) + ); + } +); describe('TermBoostList', () => { beforeEach(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.tsx index cdf9234ce20e..120fa2681bdb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/TermBoostList/TermBoostList.tsx @@ -12,12 +12,11 @@ */ import classNames from 'classnames'; import { TermBoost } from '../../../generated/configuration/searchSettings'; -import TermBoostComponent from '../TermBoost/TermBoost'; +import TermBoostCard from '../../discovery/personal-space/Profile/tabs/platform-settings/search/TermBoostCard'; import './term-boost-list.less'; interface TermBoostListProps { className?: string; - termBoostCardClassName?: string; termBoosts: TermBoost[]; showNewTermBoost?: boolean; handleDeleteTermBoost: (value: string) => void; @@ -26,7 +25,6 @@ interface TermBoostListProps { const TermBoostList = ({ className, - termBoostCardClassName, termBoosts, showNewTermBoost, handleDeleteTermBoost, @@ -39,21 +37,17 @@ const TermBoostList = ({ 'd-flex items-center gap-2 flex-wrap m-b-xs term-boosts-container' )}> {termBoosts.map((termBoost) => ( - ))} {showNewTermBoost && ( - handleDeleteTermBoost('')} - onTermBoostChange={handleTermBoostChange} + handleDeleteTermBoost('')} /> )} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/SearchSettingsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/SearchSettingsPage.tsx index 7b5cc1d0c5e0..cb81c5bcd3c2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/SearchSettingsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/SearchSettingsPage.tsx @@ -142,7 +142,6 @@ const SearchBoostsSection = ({ handleDeleteTermBoost={onDeleteTermBoost} handleTermBoostChange={onTermBoostChange} showNewTermBoost={showNewTermBoost} - termBoostCardClassName="settings-term-boost-card" termBoosts={searchConfig?.globalSettings?.termBoosts ?? []} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/search-settings.less b/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/search-settings.less index c0f26cfac2f7..1a4f612bc6fd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/search-settings.less +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SearchSettingsPage/search-settings.less @@ -163,6 +163,3 @@ } } -.settings-term-boost-card { - min-width: 300px; -} From 0952b50f48a73f0d3e3047b31a1647c4d5d0cc2d Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 13 Oct 2026 17:16:04 +0530 Subject: [PATCH 3/6] test(ui): wait for any table search when picking the query's tables --- .../resources/ui/playwright/e2e/Features/QueryEntity.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts index 6f00c9e331f9..3f2db964702b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/QueryEntity.spec.ts @@ -204,8 +204,7 @@ test('Query Entity', async ({ page }) => { const tableSearchResponse = page.waitForResponse( (response) => response.url().includes('/api/v1/search/query') && - response.url().includes('index=table') && - response.url().includes(queryData.queryUsedIn.table2) + response.url().includes('index=table') ); await page .getByTestId('edit-query-used-in') From 39e62dfe9e945453ea8ee2d70eb0c93ff82e9480 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 13 Oct 2026 17:20:48 +0530 Subject: [PATCH 4/6] fix(ui): keep query table picks and name each theme colour input The query table picker is controlled, so it now follows the user's picks instead of being reset to the saved tables on every parent re-render. ColorPicker takes the field label, so the hex box and the swatch of each theme colour get their own accessible names. --- .../QueryUsedByOtherTable.component.tsx | 63 ++++++++++--------- .../QueryUsedByOtherTable.test.tsx | 35 ++++++++++- .../ColorPicker/ColorPicker.component.tsx | 7 ++- .../common/ColorPicker/ColorPicker.test.tsx | 24 ++++++- .../AppearanceConfigSettingsPage.tsx | 1 + 5 files changed, 94 insertions(+), 36 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx index 39df36b9b43d..6325944d50f9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.component.tsx @@ -18,7 +18,7 @@ import { Typography, } from '@openmetadata/ui-core-components'; import { isUndefined, slice } from 'lodash'; -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { QUERY_USED_BY_TABLE_VIEW_CAP } from '../../../../constants/Query.constant'; @@ -121,38 +121,43 @@ const QueryUsedByOtherTable = ({ [topThreeTable, remainingTable] ); - const selectList = useMemo(() => { - const initialOptions: DataAssetOption[] = (query.queryUsedIn ?? []).map( - (table) => ({ + const initialOptions = useMemo( + () => + (query.queryUsedIn ?? []).map((table) => ({ value: table.fullyQualifiedName, displayName: getEntityName(table), reference: table, - }) - ); + })), + [query.queryUsedIn] + ); + // The picker is controlled, so it must be fed the user's picks rather than + // the saved tables, or each re-render would reset the selection. + const [selectedOptions, setSelectedOptions] = useState(initialOptions); + + useEffect(() => { + setSelectedOptions(initialOptions); + }, [initialOptions]); - return ( -
- option.value ?? '')} - onChange={(options) => - onChange( - (Array.isArray(options) ? options : []).map( - (option) => option.reference - ) - ) - } - /> -
- ); - }, [query.queryUsedIn, onChange, t]); + const selectList = ( +
+ { + const next = Array.isArray(options) ? options : []; + setSelectedOptions(next); + onChange(next.map((option) => option.reference)); + }} + /> +
+ ); return ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx index 0195a2a55613..909e5e23813c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/TableQueries/QueryUsedByOtherTable/QueryUsedByOtherTable.test.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { SearchIndex } from '../../../../enums/search.enum'; import { Query } from '../../../../generated/entity/data/query'; @@ -82,7 +82,9 @@ describe('QueryUsedByOtherTable test', () => { expect(props.searchIndex).toBe(SearchIndex.TABLE); expect(props.multiple).toBe(true); - expect(props.value).toEqual(usedIn.map((t) => t.fullyQualifiedName)); + expect( + props.value.map((option: { value?: string }) => option.value) + ).toEqual(usedIn.map((t) => t.fullyQualifiedName)); }); it('Should report the picked tables as entity references', async () => { @@ -100,4 +102,33 @@ describe('QueryUsedByOtherTable test', () => { expect(onChange).toHaveBeenCalledWith([reference]); }); + + it('Should keep the picked tables when the parent re-renders', async () => { + const { rerender } = render( + , + { wrapper: MemoryRouter } + ); + await screen.findByText('DataAssetAsyncSelectList'); + + const picked = [ + { + displayName: 'orders', + value: 'db.orders', + reference: { id: 'orders-id', type: 'table' }, + }, + ]; + act(() => { + mockDataAssetSelect.mock.calls[0][0].onChange(picked); + }); + rerender( + + ); + + const lastProps = + mockDataAssetSelect.mock.calls[ + mockDataAssetSelect.mock.calls.length - 1 + ][0]; + + expect(lastProps.value).toEqual(picked); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx index f363fb1b6d6c..e61ed43c846b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx @@ -15,6 +15,8 @@ import { useTranslation } from 'react-i18next'; export interface ColorPickerProps { id?: string; + /** Names both controls, so each theme colour is distinguishable. */ + label: string; value?: string; onBlur?: () => void; onChange?: (value: string) => void; @@ -22,6 +24,7 @@ export interface ColorPickerProps { const ColorPicker = ({ id, + label, value = '', onBlur, onChange, @@ -33,7 +36,7 @@ const ColorPicker = ({ { it('component should render', async () => { - render(); + render(); expect(await screen.findByTestId('color-picker')).toBeInTheDocument(); expect(await screen.findByTestId('color-input')).toBeInTheDocument(); }); it('Should have same value in color picker and input box', async () => { - render(); + render(); expect(await screen.findByTestId('color-picker')).toHaveValue('#000000'); expect(await screen.findByTestId('color-input')).toHaveValue('#000000'); @@ -30,7 +30,14 @@ describe('ColorPicker component', () => { it('reports a typed hex code as a plain string', () => { const onChange = jest.fn(); - render(); + render( + + ); fireEvent.change(screen.getByTestId('primaryColor-color-input'), { target: { value: '#123456' }, @@ -38,4 +45,15 @@ describe('ColorPicker component', () => { expect(onChange).toHaveBeenCalledWith('#123456'); }); + + it('names the hex box and the swatch after the field', () => { + render(); + + expect(screen.getByTestId('color-input')).toHaveAccessibleName( + 'Primary Color' + ); + expect(screen.getByTestId('color-picker')).toHaveAccessibleName( + 'label.select-field' + ); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx index 05ce5ca01816..5de1b713dd1f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AppearanceConfigSettingsPage/AppearanceConfigSettingsPage.tsx @@ -351,6 +351,7 @@ const AppearanceConfigSettingsPage = () => { Date: Tue, 13 Oct 2026 17:26:04 +0530 Subject: [PATCH 5/6] fix(ui): keep the theme colour swatch a full-size native colour input --- .../ColorPicker/ColorPicker.component.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx index e61ed43c846b..118b78cdd461 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/ColorPicker/ColorPicker.component.tsx @@ -34,16 +34,17 @@ const ColorPicker = ({ return ( - - - + {/* Core has no colour swatch control; a core Input squeezes the native + swatch into a thin stripe, so the swatch stays a native colour input. */} + onChange?.(e.target.value)} + /> Date: Tue, 13 Oct 2026 11:57:58 +0000 Subject: [PATCH 6/6] chore(playwright): auto-refresh impact-map.generated.json --- .github/playwright/impact-map.generated.json | 138 ++----------------- 1 file changed, 12 insertions(+), 126 deletions(-) diff --git a/.github/playwright/impact-map.generated.json b/.github/playwright/impact-map.generated.json index 8ec9320b680b..60da398e6fed 100644 --- a/.github/playwright/impact-map.generated.json +++ b/.github/playwright/impact-map.generated.json @@ -6969,16 +6969,6 @@ "playwright/e2e/Pages/Lineage/LineageInteraction.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageConfigModal.tsx" - ], - "specs": [ - "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Flow/LineageSettings.spec.ts", - "playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageControlButtons/LineageControlButtons.tsx" @@ -7645,14 +7635,6 @@ "playwright/e2e/Pages/Tag.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/Modals/ModalWithFunctionEditor/ModalWithFunctionEditor.tsx" - ], - "specs": [ - "playwright/e2e/Pages/Lineage/LineageInteraction.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor.tsx" @@ -8116,14 +8098,6 @@ "playwright/e2e/Pages/SearchSettings.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/FieldValueBoostModal/FieldValueBoostModal.tsx" - ], - "specs": [ - "playwright/e2e/Pages/SearchSettings.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/SearchSettings/SearchPreview/SearchPreview.tsx" @@ -8363,8 +8337,7 @@ "openmetadata-ui/src/main/resources/ui/src/components/Settings/Email/EmailConfigForm/EmailConfigForm.component.tsx" ], "specs": [ - "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Pages/EmailConfiguration.spec.ts" + "playwright/e2e/Features/ProfilePlatformSettings.spec.ts" ] }, { @@ -8615,17 +8588,11 @@ "openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/CreateUser/CreateUser.component.tsx" ], "specs": [ - "playwright/e2e/Features/ContextCenterPermission.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryDisplayNameEdit.spec.ts", "playwright/e2e/Features/QueryEntity.spec.ts", "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", "playwright/e2e/Pages/AIProfile/ProfileMembers.spec.ts", "playwright/e2e/Pages/AIProfile/ProfileUser.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", - "playwright/e2e/Pages/DataContracts.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts" + "playwright/e2e/Pages/DataContracts.spec.ts" ] }, { @@ -10893,6 +10860,14 @@ "playwright/e2e/Pages/SearchSettings.spec.ts" ] }, + { + "sources": [ + "openmetadata-ui/src/main/resources/ui/src/components/discovery/personal-space/Profile/tabs/platform-settings/search/FieldValueBoostDialog.tsx" + ], + "specs": [ + "playwright/e2e/Pages/SearchSettings.spec.ts" + ] + }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/components/discovery/personal-space/Profile/tabs/platform-settings/search/FieldValueBoostTable.tsx" @@ -12464,14 +12439,6 @@ "playwright/e2e/Pages/DataProducts.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/Configuration/EditUrlConfiguration/EditUrlConfigurationPage.tsx" - ], - "specs": [ - "playwright/e2e/Pages/OmdURLConfiguration.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/Configuration/LoginConfigurationDetails/LoginConfigurationPage.tsx" @@ -12713,36 +12680,6 @@ "playwright/e2e/Pages/IntakeForm.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/AddKPIPage.tsx" - ], - "specs": [ - "playwright/e2e/Features/ContextCenterPermission.spec.ts", - "playwright/e2e/Features/DataQuality/ProfilerSettingsAfterReindex.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryDisplayNameEdit.spec.ts", - "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts" - ] - }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/KPIPage/EditKPIPage.tsx" - ], - "specs": [ - "playwright/e2e/Features/ContextCenterPermission.spec.ts", - "playwright/e2e/Features/DataQuality/ProfilerSettingsAfterReindex.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryDisplayNameEdit.spec.ts", - "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/KnowledgeCenterFilterPage/KnowledgeCenterFilterPage.tsx" @@ -12756,10 +12693,6 @@ "openmetadata-ui/src/main/resources/ui/src/pages/LearningResourcesPage/LearningResourceForm.component.tsx" ], "specs": [ - "playwright/e2e/Features/IncidentManager.spec.ts", - "playwright/e2e/Features/ProfileGovernance.spec.ts", - "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Pages/GlossaryTermRelationSettings.spec.ts", "playwright/e2e/Pages/LearningResources.spec.ts" ] }, @@ -12779,8 +12712,7 @@ "specs": [ "playwright/e2e/Features/MetricHierarchy.spec.ts", "playwright/e2e/Features/ProfilePlatformSettings.spec.ts", - "playwright/e2e/Flow/LineageSettings.spec.ts", - "playwright/e2e/Pages/Lineage/PlatformLineage.spec.ts" + "playwright/e2e/Flow/LineageSettings.spec.ts" ] }, { @@ -12858,8 +12790,7 @@ "openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/AddPolicyPage/AddPolicyPage.tsx" ], "specs": [ - "playwright/e2e/Features/AccessControlSettings.spec.ts", - "playwright/e2e/Pages/Policies.spec.ts" + "playwright/e2e/Features/AccessControlSettings.spec.ts" ] }, { @@ -12881,15 +12812,6 @@ "playwright/e2e/Pages/Policies.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/PoliciesPage/RuleForm/RuleForm.tsx" - ], - "specs": [ - "playwright/e2e/Features/AccessControlSettings.spec.ts", - "playwright/e2e/Pages/Policies.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/ProfilerConfigurationPage/ProfilerConfigurationPage.tsx" @@ -12914,20 +12836,8 @@ ], "specs": [ "playwright/e2e/Features/AccessControlSettings.spec.ts", - "playwright/e2e/Features/EntityRenameConsolidation.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMiscOperations.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMutualExclusivityDataProductTree.spec.ts", - "playwright/e2e/Features/MetricCustomUnitFlow.spec.ts", - "playwright/e2e/Features/MetricHierarchy.spec.ts", - "playwright/e2e/Features/MultipleRename.spec.ts", "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileTeam.spec.ts", - "playwright/e2e/Pages/DataProductAndSubdomains.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/IntakeForm.spec.ts", "playwright/e2e/Pages/Roles.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts", "playwright/e2e/Pages/Teams.spec.ts" ] }, @@ -13181,30 +13091,6 @@ "playwright/e2e/Pages/GlossaryImportExport.spec.ts" ] }, - { - "sources": [ - "openmetadata-ui/src/main/resources/ui/src/pages/TeamsPage/AddTeamForm.tsx" - ], - "specs": [ - "playwright/e2e/Features/AIMode/Observability.spec.ts", - "playwright/e2e/Features/DataQuality/TestLibrary.spec.ts", - "playwright/e2e/Features/EntityRenameConsolidation.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMiscOperations.spec.ts", - "playwright/e2e/Features/Glossary/GlossaryMutualExclusivityDataProductTree.spec.ts", - "playwright/e2e/Features/MetricCustomUnitFlow.spec.ts", - "playwright/e2e/Features/MetricHierarchy.spec.ts", - "playwright/e2e/Features/MultipleRename.spec.ts", - "playwright/e2e/Flow/AddRoleAndAssignToUser.spec.ts", - "playwright/e2e/Flow/PersonaFlow.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileTeam.spec.ts", - "playwright/e2e/Pages/AIProfile/ProfileUser.spec.ts", - "playwright/e2e/Pages/BotsProfile.spec.ts", - "playwright/e2e/Pages/DataProductAndSubdomains.spec.ts", - "playwright/e2e/Pages/EditClassification.spec.ts", - "playwright/e2e/Pages/IntakeForm.spec.ts", - "playwright/e2e/Pages/Tags.spec.ts" - ] - }, { "sources": [ "openmetadata-ui/src/main/resources/ui/src/pages/TestSuiteDetailsPage/TestSuiteDetailsPage.component.tsx"